Utils.mjs 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223
  1. /**
  2. * @author n1474335 [n1474335@gmail.com]
  3. * @copyright Crown Copyright 2016
  4. * @license Apache-2.0
  5. */
  6. import utf8 from "utf8";
  7. import moment from "moment-timezone";
  8. import {fromBase64} from "./lib/Base64";
  9. import {toHexFast, fromHex} from "./lib/Hex";
  10. /**
  11. * Utility functions for use in operations, the core framework and the stage.
  12. */
  13. class Utils {
  14. /**
  15. * Translates an ordinal into a character.
  16. *
  17. * @param {number} o
  18. * @returns {char}
  19. *
  20. * @example
  21. * // returns 'a'
  22. * Utils.chr(97);
  23. */
  24. static chr(o) {
  25. // Detect astral symbols
  26. // Thanks to @mathiasbynens for this solution
  27. // https://mathiasbynens.be/notes/javascript-unicode
  28. if (o > 0xffff) {
  29. o -= 0x10000;
  30. const high = String.fromCharCode(o >>> 10 & 0x3ff | 0xd800);
  31. o = 0xdc00 | o & 0x3ff;
  32. return high + String.fromCharCode(o);
  33. }
  34. return String.fromCharCode(o);
  35. }
  36. /**
  37. * Translates a character into an ordinal.
  38. *
  39. * @param {char} c
  40. * @returns {number}
  41. *
  42. * @example
  43. * // returns 97
  44. * Utils.ord('a');
  45. */
  46. static ord(c) {
  47. // Detect astral symbols
  48. // Thanks to @mathiasbynens for this solution
  49. // https://mathiasbynens.be/notes/javascript-unicode
  50. if (c.length === 2) {
  51. const high = c.charCodeAt(0);
  52. const low = c.charCodeAt(1);
  53. if (high >= 0xd800 && high < 0xdc00 &&
  54. low >= 0xdc00 && low < 0xe000) {
  55. return (high - 0xd800) * 0x400 + low - 0xdc00 + 0x10000;
  56. }
  57. }
  58. return c.charCodeAt(0);
  59. }
  60. /**
  61. * Adds trailing bytes to a byteArray.
  62. *
  63. * @author tlwr [toby@toby.codes]
  64. *
  65. * @param {byteArray} arr - byteArray to add trailing bytes to.
  66. * @param {number} numBytes - Maximum width of the array.
  67. * @param {Integer} [padByte=0] - The byte to pad with.
  68. * @returns {byteArray}
  69. *
  70. * @example
  71. * // returns ["a", 0, 0, 0]
  72. * Utils.padBytesRight("a", 4);
  73. *
  74. * // returns ["a", 1, 1, 1]
  75. * Utils.padBytesRight("a", 4, 1);
  76. *
  77. * // returns ["t", "e", "s", "t", 0, 0, 0, 0]
  78. * Utils.padBytesRight("test", 8);
  79. *
  80. * // returns ["t", "e", "s", "t", 1, 1, 1, 1]
  81. * Utils.padBytesRight("test", 8, 1);
  82. */
  83. static padBytesRight(arr, numBytes, padByte=0) {
  84. const paddedBytes = new Array(numBytes);
  85. paddedBytes.fill(padByte);
  86. Array.prototype.map.call(arr, function(b, i) {
  87. paddedBytes[i] = b;
  88. });
  89. return paddedBytes;
  90. }
  91. /**
  92. * Truncates a long string to max length and adds suffix.
  93. *
  94. * @param {string} str - String to truncate
  95. * @param {number} max - Maximum length of the final string
  96. * @param {string} [suffix='...'] - The string to add to the end of the final string
  97. * @returns {string}
  98. *
  99. * @example
  100. * // returns "A long..."
  101. * Utils.truncate("A long string", 9);
  102. *
  103. * // returns "A long s-"
  104. * Utils.truncate("A long string", 9, "-");
  105. */
  106. static truncate(str, max, suffix="...") {
  107. if (str.length > max) {
  108. str = str.slice(0, max - suffix.length) + suffix;
  109. }
  110. return str;
  111. }
  112. /**
  113. * Converts a character or number to its hex representation.
  114. *
  115. * @param {char|number} c
  116. * @param {number} [length=2] - The width of the resulting hex number.
  117. * @returns {string}
  118. *
  119. * @example
  120. * // returns "6e"
  121. * Utils.hex("n");
  122. *
  123. * // returns "6e"
  124. * Utils.hex(110);
  125. */
  126. static hex(c, length=2) {
  127. c = typeof c == "string" ? Utils.ord(c) : c;
  128. return c.toString(16).padStart(length, "0");
  129. }
  130. /**
  131. * Converts a character or number to its binary representation.
  132. *
  133. * @param {char|number} c
  134. * @param {number} [length=8] - The width of the resulting binary number.
  135. * @returns {string}
  136. *
  137. * @example
  138. * // returns "01101110"
  139. * Utils.bin("n");
  140. *
  141. * // returns "01101110"
  142. * Utils.bin(110);
  143. */
  144. static bin(c, length=8) {
  145. c = typeof c == "string" ? Utils.ord(c) : c;
  146. return c.toString(2).padStart(length, "0");
  147. }
  148. /**
  149. * Returns a string with all non-printable chars as dots, optionally preserving whitespace.
  150. *
  151. * @param {string} str - The input string to display.
  152. * @param {boolean} [preserveWs=false] - Whether or not to print whitespace.
  153. * @returns {string}
  154. */
  155. static printable(str, preserveWs=false) {
  156. if (ENVIRONMENT_IS_WEB() && window.app && !window.app.options.treatAsUtf8) {
  157. str = Utils.byteArrayToChars(Utils.strToByteArray(str));
  158. }
  159. const re = /[\0-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uE000-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g;
  160. const wsRe = /[\x09-\x10\x0D\u2028\u2029]/g;
  161. str = str.replace(re, ".");
  162. if (!preserveWs) str = str.replace(wsRe, ".");
  163. return str;
  164. }
  165. /**
  166. * Parse a string entered by a user and replace escaped chars with the bytes they represent.
  167. *
  168. * @param {string} str
  169. * @returns {string}
  170. *
  171. * @example
  172. * // returns "\x00"
  173. * Utils.parseEscapedChars("\\x00");
  174. *
  175. * // returns "\n"
  176. * Utils.parseEscapedChars("\\n");
  177. */
  178. static parseEscapedChars(str) {
  179. return str.replace(/(\\)?\\([bfnrtv0'"]|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\})/g, function(m, a, b) {
  180. if (a === "\\") return "\\"+b;
  181. switch (b[0]) {
  182. case "0":
  183. return "\0";
  184. case "b":
  185. return "\b";
  186. case "t":
  187. return "\t";
  188. case "n":
  189. return "\n";
  190. case "v":
  191. return "\v";
  192. case "f":
  193. return "\f";
  194. case "r":
  195. return "\r";
  196. case '"':
  197. return '"';
  198. case "'":
  199. return "'";
  200. case "x":
  201. return String.fromCharCode(parseInt(b.substr(1), 16));
  202. case "u":
  203. if (b[1] === "{")
  204. return String.fromCodePoint(parseInt(b.slice(2, -1), 16));
  205. else
  206. return String.fromCharCode(parseInt(b.substr(1), 16));
  207. }
  208. });
  209. }
  210. /**
  211. * Escape a string containing regex control characters so that it can be safely
  212. * used in a regex without causing unintended behaviours.
  213. *
  214. * @param {string} str
  215. * @returns {string}
  216. *
  217. * @example
  218. * // returns "\[example\]"
  219. * Utils.escapeRegex("[example]");
  220. */
  221. static escapeRegex(str) {
  222. return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, "\\$1");
  223. }
  224. /**
  225. * Expand an alphabet range string into a list of the characters in that range.
  226. *
  227. * @param {string} alphStr
  228. * @returns {char[]}
  229. *
  230. * @example
  231. * // returns ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
  232. * Utils.expandAlphRange("0-9");
  233. *
  234. * // returns ["a", "b", "c", "d", "0", "1", "2", "3", "+", "/"]
  235. * Utils.expandAlphRange("a-d0-3+/");
  236. *
  237. * // returns ["a", "b", "c", "d", "0", "-", "3"]
  238. * Utils.expandAlphRange("a-d0\\-3")
  239. */
  240. static expandAlphRange(alphStr) {
  241. const alphArr = [];
  242. for (let i = 0; i < alphStr.length; i++) {
  243. if (i < alphStr.length - 2 &&
  244. alphStr[i+1] === "-" &&
  245. alphStr[i] !== "\\") {
  246. const start = Utils.ord(alphStr[i]),
  247. end = Utils.ord(alphStr[i+2]);
  248. for (let j = start; j <= end; j++) {
  249. alphArr.push(Utils.chr(j));
  250. }
  251. i += 2;
  252. } else if (i < alphStr.length - 2 &&
  253. alphStr[i] === "\\" &&
  254. alphStr[i+1] === "-") {
  255. alphArr.push("-");
  256. i++;
  257. } else {
  258. alphArr.push(alphStr[i]);
  259. }
  260. }
  261. return alphArr;
  262. }
  263. /**
  264. * Coverts data of varying types to a byteArray.
  265. * Accepts hex, Base64, UTF8 and Latin1 strings.
  266. *
  267. * @param {string} str
  268. * @param {string} type - One of "Hex", "Base64", "UTF8" or "Latin1"
  269. * @returns {byteArray}
  270. *
  271. * @example
  272. * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
  273. * Utils.convertToByteArray("Привет", "utf8");
  274. *
  275. * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
  276. * Utils.convertToByteArray("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex");
  277. *
  278. * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
  279. * Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64");
  280. */
  281. static convertToByteArray(str, type) {
  282. switch (type.toLowerCase()) {
  283. case "hex":
  284. return fromHex(str);
  285. case "base64":
  286. return fromBase64(str, null, "byteArray");
  287. case "utf8":
  288. return Utils.strToUtf8ByteArray(str);
  289. case "latin1":
  290. default:
  291. return Utils.strToByteArray(str);
  292. }
  293. }
  294. /**
  295. * Coverts data of varying types to a byte string.
  296. * Accepts hex, Base64, UTF8 and Latin1 strings.
  297. *
  298. * @param {string} str
  299. * @param {string} type - One of "Hex", "Base64", "UTF8" or "Latin1"
  300. * @returns {string}
  301. *
  302. * @example
  303. * // returns "Привет"
  304. * Utils.convertToByteString("Привет", "utf8");
  305. *
  306. * // returns "Здравствуйте"
  307. * Utils.convertToByteString("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex");
  308. *
  309. * // returns "Здравствуйте"
  310. * Utils.convertToByteString("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64");
  311. */
  312. static convertToByteString(str, type) {
  313. switch (type.toLowerCase()) {
  314. case "hex":
  315. return Utils.byteArrayToChars(fromHex(str));
  316. case "base64":
  317. return Utils.byteArrayToChars(fromBase64(str, null, "byteArray"));
  318. case "utf8":
  319. return utf8.encode(str);
  320. case "latin1":
  321. default:
  322. return str;
  323. }
  324. }
  325. /**
  326. * Converts a string to a byte array.
  327. * Treats the string as UTF-8 if any values are over 255.
  328. *
  329. * @param {string} str
  330. * @returns {byteArray}
  331. *
  332. * @example
  333. * // returns [72,101,108,108,111]
  334. * Utils.strToByteArray("Hello");
  335. *
  336. * // returns [228,189,160,229,165,189]
  337. * Utils.strToByteArray("你好");
  338. */
  339. static strToByteArray(str) {
  340. const byteArray = new Array(str.length);
  341. let i = str.length, b;
  342. while (i--) {
  343. b = str.charCodeAt(i);
  344. byteArray[i] = b;
  345. // If any of the bytes are over 255, read as UTF-8
  346. if (b > 255) return Utils.strToUtf8ByteArray(str);
  347. }
  348. return byteArray;
  349. }
  350. /**
  351. * Converts a string to a UTF-8 byte array.
  352. *
  353. * @param {string} str
  354. * @returns {byteArray}
  355. *
  356. * @example
  357. * // returns [72,101,108,108,111]
  358. * Utils.strToUtf8ByteArray("Hello");
  359. *
  360. * // returns [228,189,160,229,165,189]
  361. * Utils.strToUtf8ByteArray("你好");
  362. */
  363. static strToUtf8ByteArray(str) {
  364. const utf8Str = utf8.encode(str);
  365. if (str.length !== utf8Str.length) {
  366. if (ENVIRONMENT_IS_WORKER()) {
  367. self.setOption("attemptHighlight", false);
  368. } else if (ENVIRONMENT_IS_WEB()) {
  369. window.app.options.attemptHighlight = false;
  370. }
  371. }
  372. return Utils.strToByteArray(utf8Str);
  373. }
  374. /**
  375. * Converts a string to a unicode charcode array
  376. *
  377. * @param {string} str
  378. * @returns {byteArray}
  379. *
  380. * @example
  381. * // returns [72,101,108,108,111]
  382. * Utils.strToCharcode("Hello");
  383. *
  384. * // returns [20320,22909]
  385. * Utils.strToCharcode("你好");
  386. */
  387. static strToCharcode(str) {
  388. const charcode = [];
  389. for (let i = 0; i < str.length; i++) {
  390. let ord = str.charCodeAt(i);
  391. // Detect and merge astral symbols
  392. if (i < str.length - 1 && ord >= 0xd800 && ord < 0xdc00) {
  393. const low = str[i + 1].charCodeAt(0);
  394. if (low >= 0xdc00 && low < 0xe000) {
  395. ord = Utils.ord(str[i] + str[++i]);
  396. }
  397. }
  398. charcode.push(ord);
  399. }
  400. return charcode;
  401. }
  402. /**
  403. * Attempts to convert a byte array to a UTF-8 string.
  404. *
  405. * @param {byteArray} byteArray
  406. * @returns {string}
  407. *
  408. * @example
  409. * // returns "Hello"
  410. * Utils.byteArrayToUtf8([72,101,108,108,111]);
  411. *
  412. * // returns "你好"
  413. * Utils.byteArrayToUtf8([228,189,160,229,165,189]);
  414. */
  415. static byteArrayToUtf8(byteArray) {
  416. const str = Utils.byteArrayToChars(byteArray);
  417. try {
  418. const utf8Str = utf8.decode(str);
  419. if (str.length !== utf8Str.length) {
  420. if (ENVIRONMENT_IS_WORKER()) {
  421. self.setOption("attemptHighlight", false);
  422. } else if (ENVIRONMENT_IS_WEB()) {
  423. window.app.options.attemptHighlight = false;
  424. }
  425. }
  426. return utf8Str;
  427. } catch (err) {
  428. // If it fails, treat it as ANSI
  429. return str;
  430. }
  431. }
  432. /**
  433. * Converts a charcode array to a string.
  434. *
  435. * @param {byteArray|Uint8Array} byteArray
  436. * @returns {string}
  437. *
  438. * @example
  439. * // returns "Hello"
  440. * Utils.byteArrayToChars([72,101,108,108,111]);
  441. *
  442. * // returns "你好"
  443. * Utils.byteArrayToChars([20320,22909]);
  444. */
  445. static byteArrayToChars(byteArray) {
  446. if (!byteArray) return "";
  447. let str = "";
  448. for (let i = 0; i < byteArray.length;) {
  449. str += String.fromCharCode(byteArray[i++]);
  450. }
  451. return str;
  452. }
  453. /**
  454. * Converts an ArrayBuffer to a string.
  455. *
  456. * @param {ArrayBuffer} arrayBuffer
  457. * @param {boolean} [utf8=true] - Whether to attempt to decode the buffer as UTF-8
  458. * @returns {string}
  459. *
  460. * @example
  461. * // returns "hello"
  462. * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer);
  463. */
  464. static arrayBufferToStr(arrayBuffer, utf8=true) {
  465. const byteArray = Array.prototype.slice.call(new Uint8Array(arrayBuffer));
  466. return utf8 ? Utils.byteArrayToUtf8(byteArray) : Utils.byteArrayToChars(byteArray);
  467. }
  468. /**
  469. * Parses CSV data and returns it as a two dimensional array or strings.
  470. *
  471. * @param {string} data
  472. * @returns {string[][]}
  473. *
  474. * @example
  475. * // returns [["head1", "head2"], ["data1", "data2"]]
  476. * Utils.parseCSV("head1,head2\ndata1,data2");
  477. */
  478. static parseCSV(data) {
  479. let b,
  480. ignoreNext = false,
  481. inString = false,
  482. cell = "",
  483. line = [];
  484. const lines = [];
  485. for (let i = 0; i < data.length; i++) {
  486. b = data[i];
  487. if (ignoreNext) {
  488. cell += b;
  489. ignoreNext = false;
  490. } else if (b === "\\") {
  491. cell += b;
  492. ignoreNext = true;
  493. } else if (b === "\"" && !inString) {
  494. inString = true;
  495. } else if (b === "\"" && inString) {
  496. inString = false;
  497. } else if (b === "," && !inString) {
  498. line.push(cell);
  499. cell = "";
  500. } else if ((b === "\n" || b === "\r") && !inString) {
  501. line.push(cell);
  502. cell = "";
  503. lines.push(line);
  504. line = [];
  505. } else {
  506. cell += b;
  507. }
  508. }
  509. if (line.length) {
  510. line.push(cell);
  511. lines.push(line);
  512. }
  513. return lines;
  514. }
  515. /**
  516. * Removes all HTML (or XML) tags from the input string.
  517. *
  518. * @param {string} htmlStr
  519. * @param {boolean} [removeScriptAndStyle=false]
  520. * - Flag to specify whether to remove entire script or style blocks
  521. * @returns {string}
  522. *
  523. * @example
  524. * // returns "Test"
  525. * Utils.stripHtmlTags("<div>Test</div>");
  526. */
  527. static stripHtmlTags(htmlStr, removeScriptAndStyle=false) {
  528. if (removeScriptAndStyle) {
  529. htmlStr = htmlStr.replace(/<(script|style)[^>]*>.*<\/(script|style)>/gmi, "");
  530. }
  531. return htmlStr.replace(/<[^>]+>/g, "");
  532. }
  533. /**
  534. * Escapes HTML tags in a string to stop them being rendered.
  535. * https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
  536. *
  537. * @param {string} str
  538. * @returns string
  539. *
  540. * @example
  541. * // return "A &lt;script&gt; tag"
  542. * Utils.escapeHtml("A <script> tag");
  543. */
  544. static escapeHtml(str) {
  545. const HTML_CHARS = {
  546. "&": "&amp;",
  547. "<": "&lt;",
  548. ">": "&gt;",
  549. '"': "&quot;",
  550. "'": "&#x27;", // &apos; not recommended because it's not in the HTML spec
  551. "/": "&#x2F;", // forward slash is included as it helps end an HTML entity
  552. "`": "&#x60;"
  553. };
  554. return str.replace(/[&<>"'/`]/g, function (match) {
  555. return HTML_CHARS[match];
  556. });
  557. }
  558. /**
  559. * Unescapes HTML tags in a string to make them render again.
  560. *
  561. * @param {string} str
  562. * @returns string
  563. *
  564. * @example
  565. * // return "A <script> tag"
  566. * Utils.unescapeHtml("A &lt;script&gt; tag");
  567. */
  568. static unescapeHtml(str) {
  569. const HTML_CHARS = {
  570. "&amp;": "&",
  571. "&lt;": "<",
  572. "&gt;": ">",
  573. "&quot;": '"',
  574. "&#x27;": "'",
  575. "&#x2F;": "/",
  576. "&#x60;": "`"
  577. };
  578. return str.replace(/&#?x?[a-z0-9]{2,4};/ig, function (match) {
  579. return HTML_CHARS[match] || match;
  580. });
  581. }
  582. /**
  583. * Encodes a URI fragment (#) or query (?) using a minimal amount of percent-encoding.
  584. *
  585. * RFC 3986 defines legal characters for the fragment and query parts of a URL to be as follows:
  586. *
  587. * fragment = *( pchar / "/" / "?" )
  588. * query = *( pchar / "/" / "?" )
  589. * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
  590. * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
  591. * pct-encoded = "%" HEXDIG HEXDIG
  592. * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
  593. * / "*" / "+" / "," / ";" / "="
  594. *
  595. * Meaning that the list of characters that need not be percent-encoded are alphanumeric plus:
  596. * -._~!$&'()*+,;=:@/?
  597. *
  598. * & and = are still escaped as they are used to serialise the key-value pairs in CyberChef
  599. * fragments. + is also escaped so as to prevent it being decoded to a space.
  600. *
  601. * @param {string} str
  602. * @returns {string}
  603. */
  604. static encodeURIFragment(str) {
  605. const LEGAL_CHARS = {
  606. "%2D": "-",
  607. "%2E": ".",
  608. "%5F": "_",
  609. "%7E": "~",
  610. "%21": "!",
  611. "%24": "$",
  612. //"%26": "&",
  613. "%27": "'",
  614. "%28": "(",
  615. "%29": ")",
  616. "%2A": "*",
  617. //"%2B": "+",
  618. "%2C": ",",
  619. "%3B": ";",
  620. //"%3D": "=",
  621. "%3A": ":",
  622. "%40": "@",
  623. "%2F": "/",
  624. "%3F": "?"
  625. };
  626. str = encodeURIComponent(str);
  627. return str.replace(/%[0-9A-F]{2}/g, function (match) {
  628. return LEGAL_CHARS[match] || match;
  629. });
  630. }
  631. /**
  632. * Generates a "pretty" recipe format from a recipeConfig object.
  633. *
  634. * "Pretty" CyberChef recipe formats are designed to be included in the fragment (#) or query (?)
  635. * parts of the URL. They can also be loaded into CyberChef through the 'Load' interface. In order
  636. * to make this format as readable as possible, various special characters are used unescaped. This
  637. * reduces the amount of percent-encoding included in the URL which is typically difficult to read
  638. * and substantially increases the overall length. These characteristics can be quite off-putting
  639. * for users.
  640. *
  641. * @param {Object[]} recipeConfig
  642. * @param {boolean} [newline=false] - whether to add a newline after each operation
  643. * @returns {string}
  644. */
  645. static generatePrettyRecipe(recipeConfig, newline=false) {
  646. let prettyConfig = "",
  647. name = "",
  648. args = "",
  649. disabled = "",
  650. bp = "";
  651. recipeConfig.forEach(op => {
  652. name = op.op.replace(/ /g, "_");
  653. args = JSON.stringify(op.args)
  654. .slice(1, -1) // Remove [ and ] as they are implied
  655. // We now need to switch double-quoted (") strings to single quotes (') as single quotes
  656. // do not need to be percent-encoded.
  657. .replace(/'/g, "\\'") // Escape single quotes
  658. .replace(/"((?:[^"\\]|\\.)*)"/g, "'$1'") // Replace opening and closing " with '
  659. .replace(/\\"/g, '"'); // Unescape double quotes
  660. disabled = op.disabled ? "/disabled": "";
  661. bp = op.breakpoint ? "/breakpoint" : "";
  662. prettyConfig += `${name}(${args}${disabled}${bp})`;
  663. if (newline) prettyConfig += "\n";
  664. });
  665. return prettyConfig;
  666. }
  667. /**
  668. * Converts a recipe string to the JSON representation of the recipe.
  669. * Accepts either stringified JSON or the bespoke "pretty" recipe format.
  670. *
  671. * @param {string} recipe
  672. * @returns {Object[]}
  673. */
  674. static parseRecipeConfig(recipe) {
  675. recipe = recipe.trim();
  676. if (recipe.length === 0) return [];
  677. if (recipe[0] === "[") return JSON.parse(recipe);
  678. // Parse bespoke recipe format
  679. recipe = recipe.replace(/\n/g, "");
  680. let m, args;
  681. const recipeRegex = /([^(]+)\(((?:'[^'\\]*(?:\\.[^'\\]*)*'|[^)/'])*)(\/[^)]+)?\)/g,
  682. recipeConfig = [];
  683. while ((m = recipeRegex.exec(recipe))) {
  684. // Translate strings in args back to double-quotes
  685. args = m[2]
  686. .replace(/"/g, '\\"') // Escape double quotes
  687. .replace(/(^|,|{|:)'/g, '$1"') // Replace opening ' with "
  688. .replace(/([^\\])'(,|:|}|$)/g, '$1"$2') // Replace closing ' with "
  689. .replace(/\\'/g, "'"); // Unescape single quotes
  690. args = "[" + args + "]";
  691. const op = {
  692. op: m[1].replace(/_/g, " "),
  693. args: JSON.parse(args)
  694. };
  695. if (m[3] && m[3].indexOf("disabled") > 0) op.disabled = true;
  696. if (m[3] && m[3].indexOf("breakpoint") > 0) op.breakpoint = true;
  697. recipeConfig.push(op);
  698. }
  699. return recipeConfig;
  700. }
  701. /**
  702. * Expresses a number of milliseconds in a human readable format.
  703. *
  704. * Range | Sample Output
  705. * -----------------------------|-------------------------------
  706. * 0 to 45 seconds | a few seconds ago
  707. * 45 to 90 seconds | a minute ago
  708. * 90 seconds to 45 minutes | 2 minutes ago ... 45 minutes ago
  709. * 45 to 90 minutes | an hour ago
  710. * 90 minutes to 22 hours | 2 hours ago ... 22 hours ago
  711. * 22 to 36 hours | a day ago
  712. * 36 hours to 25 days | 2 days ago ... 25 days ago
  713. * 25 to 45 days | a month ago
  714. * 45 to 345 days | 2 months ago ... 11 months ago
  715. * 345 to 545 days (1.5 years) | a year ago
  716. * 546 days+ | 2 years ago ... 20 years ago
  717. *
  718. * @param {number} ms
  719. * @returns {string}
  720. *
  721. * @example
  722. * // returns "3 minutes"
  723. * Utils.fuzzyTime(152435);
  724. *
  725. * // returns "5 days"
  726. * Utils.fuzzyTime(456851321);
  727. */
  728. static fuzzyTime(ms) {
  729. return moment.duration(ms, "milliseconds").humanize();
  730. }
  731. /**
  732. * Formats a list of files or directories.
  733. *
  734. * @author tlwr [toby@toby.codes]
  735. * @author n1474335 [n1474335@gmail.com]
  736. *
  737. * @param {File[]} files
  738. * @returns {html}
  739. */
  740. static async displayFilesAsHTML(files) {
  741. const formatDirectory = function(file) {
  742. const html = `<div class='panel panel-default' style='white-space: normal;'>
  743. <div class='panel-heading' role='tab'>
  744. <h4 class='panel-title'>
  745. ${Utils.escapeHtml(file.name)}
  746. </h4>
  747. </div>
  748. </div>`;
  749. return html;
  750. };
  751. const formatFile = async function(file, i) {
  752. const buff = await Utils.readFile(file);
  753. const fileStr = Utils.arrayBufferToStr(buff.buffer);
  754. const blob = new Blob(
  755. [buff],
  756. {type: "octet/stream"}
  757. );
  758. const blobUrl = URL.createObjectURL(blob);
  759. const viewFileElem = `<a href='#collapse${i}'
  760. class='collapsed'
  761. data-toggle='collapse'
  762. aria-expanded='true'
  763. aria-controls='collapse${i}'
  764. title="Show/hide contents of '${Utils.escapeHtml(file.name)}'">&#x1f441;&#xfe0f;</a>`;
  765. const downloadFileElem = `<a href='${blobUrl}'
  766. title='Download ${Utils.escapeHtml(file.name)}'
  767. download='${Utils.escapeHtml(file.name)}'>&#x1f4be;</a>`;
  768. const hexFileData = toHexFast(buff);
  769. const switchToInputElem = `<a href='#switchFileToInput${i}'
  770. class='file-switch'
  771. title='Move file to input as hex'
  772. fileValue='${hexFileData}'>&#x21e7;</a>`;
  773. const html = `<div class='panel panel-default' style='white-space: normal;'>
  774. <div class='panel-heading' role='tab' id='heading${i}'>
  775. <h4 class='panel-title'>
  776. <div>
  777. ${Utils.escapeHtml(file.name)}
  778. ${viewFileElem}
  779. ${downloadFileElem}
  780. ${switchToInputElem}
  781. <span class='pull-right'>
  782. ${file.size.toLocaleString()} bytes
  783. </span>
  784. </div>
  785. </h4>
  786. </div>
  787. <div id='collapse${i}' class='panel-collapse collapse'
  788. role='tabpanel' aria-labelledby='heading${i}'>
  789. <div class='panel-body'>
  790. <pre><code>${Utils.escapeHtml(fileStr)}</code></pre>
  791. </div>
  792. </div>
  793. </div>`;
  794. return html;
  795. };
  796. let html = `<div style='padding: 5px; white-space: normal;'>
  797. ${files.length} file(s) found<NL>
  798. </div>`;
  799. for (let i = 0; i < files.length; i++) {
  800. if (files[i].name.endsWith("/")) {
  801. html += formatDirectory(files[i]);
  802. } else {
  803. html += await formatFile(files[i], i);
  804. }
  805. }
  806. return html;
  807. }
  808. /**
  809. * Parses URI parameters into a JSON object.
  810. *
  811. * @param {string} paramStr - The serialised query or hash section of a URI
  812. * @returns {object}
  813. *
  814. * @example
  815. * // returns {a: 'abc', b: '123'}
  816. * Utils.parseURIParams("?a=abc&b=123")
  817. * Utils.parseURIParams("#a=abc&b=123")
  818. */
  819. static parseURIParams(paramStr) {
  820. if (paramStr === "") return {};
  821. // Cut off ? or # and split on &
  822. if (paramStr[0] === "?" ||
  823. paramStr[0] === "#") {
  824. paramStr = paramStr.substr(1);
  825. }
  826. const params = paramStr.split("&");
  827. const result = {};
  828. for (let i = 0; i < params.length; i++) {
  829. const param = params[i].split("=");
  830. if (param.length !== 2) {
  831. result[params[i]] = true;
  832. } else {
  833. result[param[0]] = decodeURIComponent(param[1].replace(/\+/g, " "));
  834. }
  835. }
  836. return result;
  837. }
  838. /**
  839. * Reads a File and returns the data as a Uint8Array.
  840. *
  841. * @param {File} file
  842. * @returns {Uint8Array}
  843. *
  844. * @example
  845. * // returns Uint8Array(5) [104, 101, 108, 108, 111]
  846. * await Utils.readFile(new File(["hello"], "test"))
  847. */
  848. static readFile(file) {
  849. return new Promise((resolve, reject) => {
  850. const reader = new FileReader();
  851. const data = new Uint8Array(file.size);
  852. let offset = 0;
  853. const CHUNK_SIZE = 10485760; // 10MiB
  854. const seek = function() {
  855. if (offset >= file.size) {
  856. resolve(data);
  857. return;
  858. }
  859. const slice = file.slice(offset, offset + CHUNK_SIZE);
  860. reader.readAsArrayBuffer(slice);
  861. };
  862. reader.onload = function(e) {
  863. data.set(new Uint8Array(reader.result), offset);
  864. offset += CHUNK_SIZE;
  865. seek();
  866. };
  867. reader.onerror = function(e) {
  868. reject(reader.error.message);
  869. };
  870. seek();
  871. });
  872. }
  873. /**
  874. * Actual modulo function, since % is actually the remainder function in JS.
  875. *
  876. * @author Matt C [matt@artemisbot.uk]
  877. * @param {number} x
  878. * @param {number} y
  879. * @returns {number}
  880. */
  881. static mod(x, y) {
  882. return ((x % y) + y) % y;
  883. }
  884. /**
  885. * Finds the greatest common divisor of two numbers.
  886. *
  887. * @author Matt C [matt@artemisbot.uk]
  888. * @param {number} x
  889. * @param {number} y
  890. * @returns {number}
  891. */
  892. static gcd(x, y) {
  893. if (!y) {
  894. return x;
  895. }
  896. return Utils.gcd(y, x % y);
  897. }
  898. /**
  899. * Finds the modular inverse of two values.
  900. *
  901. * @author Matt C [matt@artemisbot.uk]
  902. * @param {number} x
  903. * @param {number} y
  904. * @returns {number}
  905. */
  906. static modInv(x, y) {
  907. x %= y;
  908. for (let i = 1; i < y; i++) {
  909. if ((x * i) % 26 === 1) {
  910. return i;
  911. }
  912. }
  913. }
  914. /**
  915. * A mapping of names of delimiter characters to their symbols.
  916. *
  917. * @param {string} token
  918. * @returns {string}
  919. */
  920. static charRep(token) {
  921. return {
  922. "Space": " ",
  923. "Comma": ",",
  924. "Semi-colon": ";",
  925. "Colon": ":",
  926. "Line feed": "\n",
  927. "CRLF": "\r\n",
  928. "Forward slash": "/",
  929. "Backslash": "\\",
  930. "0x": "0x",
  931. "\\x": "\\x",
  932. "Nothing (separate chars)": "",
  933. "None": "",
  934. }[token];
  935. }
  936. /**
  937. * A mapping of names of delimiter characters to regular expressions which can select them.
  938. *
  939. * @param {string} token
  940. * @returns {RegExp}
  941. */
  942. static regexRep(token) {
  943. return {
  944. "Space": /\s+/g,
  945. "Comma": /,/g,
  946. "Semi-colon": /;/g,
  947. "Colon": /:/g,
  948. "Line feed": /\n/g,
  949. "CRLF": /\r\n/g,
  950. "Forward slash": /\//g,
  951. "Backslash": /\\/g,
  952. "0x": /0x/g,
  953. "\\x": /\\x/g,
  954. "None": /\s+/g // Included here to remove whitespace when there shouldn't be any
  955. }[token];
  956. }
  957. }
  958. export default Utils;
  959. /**
  960. * Removes all duplicates from an array.
  961. *
  962. * @returns {Array}
  963. *
  964. * @example
  965. * // returns [3,6,4,8,2]
  966. * [3,6,4,8,4,2,3].unique();
  967. *
  968. * // returns ["One", "Two", "Three"]
  969. * ["One", "Two", "Three", "One"].unique();
  970. */
  971. Array.prototype.unique = function() {
  972. const u = {}, a = [];
  973. for (let i = 0, l = this.length; i < l; i++) {
  974. if (u.hasOwnProperty(this[i])) {
  975. continue;
  976. }
  977. a.push(this[i]);
  978. u[this[i]] = 1;
  979. }
  980. return a;
  981. };
  982. /**
  983. * Returns the largest value in the array.
  984. *
  985. * @returns {number}
  986. *
  987. * @example
  988. * // returns 7
  989. * [4,2,5,3,7].max();
  990. */
  991. Array.prototype.max = function() {
  992. return Math.max.apply(null, this);
  993. };
  994. /**
  995. * Returns the smallest value in the array.
  996. *
  997. * @returns {number}
  998. *
  999. * @example
  1000. * // returns 2
  1001. * [4,2,5,3,7].min();
  1002. */
  1003. Array.prototype.min = function() {
  1004. return Math.min.apply(null, this);
  1005. };
  1006. /**
  1007. * Sums all the values in an array.
  1008. *
  1009. * @returns {number}
  1010. *
  1011. * @example
  1012. * // returns 21
  1013. * [4,2,5,3,7].sum();
  1014. */
  1015. Array.prototype.sum = function() {
  1016. return this.reduce(function (a, b) {
  1017. return a + b;
  1018. }, 0);
  1019. };
  1020. /**
  1021. * Determine whether two arrays are equal or not.
  1022. *
  1023. * @param {Object[]} other
  1024. * @returns {boolean}
  1025. *
  1026. * @example
  1027. * // returns true
  1028. * [1,2,3].equals([1,2,3]);
  1029. *
  1030. * // returns false
  1031. * [1,2,3].equals([3,2,1]);
  1032. */
  1033. Array.prototype.equals = function(other) {
  1034. if (!other) return false;
  1035. let i = this.length;
  1036. if (i !== other.length) return false;
  1037. while (i--) {
  1038. if (this[i] !== other[i]) return false;
  1039. }
  1040. return true;
  1041. };
  1042. /**
  1043. * Counts the number of times a char appears in a string.
  1044. *
  1045. * @param {char} chr
  1046. * @returns {number}
  1047. *
  1048. * @example
  1049. * // returns 2
  1050. * "Hello".count("l");
  1051. */
  1052. String.prototype.count = function(chr) {
  1053. return this.split(chr).length - 1;
  1054. };
  1055. /*
  1056. * Polyfills
  1057. */
  1058. // https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
  1059. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
  1060. if (!String.prototype.padStart) {
  1061. String.prototype.padStart = function padStart(targetLength, padString) {
  1062. targetLength = targetLength>>0; //floor if number or convert non-number to 0;
  1063. padString = String((typeof padString !== "undefined" ? padString : " "));
  1064. if (this.length > targetLength) {
  1065. return String(this);
  1066. } else {
  1067. targetLength = targetLength-this.length;
  1068. if (targetLength > padString.length) {
  1069. padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
  1070. }
  1071. return padString.slice(0, targetLength) + String(this);
  1072. }
  1073. };
  1074. }
  1075. // https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
  1076. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
  1077. if (!String.prototype.padEnd) {
  1078. String.prototype.padEnd = function padEnd(targetLength, padString) {
  1079. targetLength = targetLength>>0; //floor if number or convert non-number to 0;
  1080. padString = String((typeof padString !== "undefined" ? padString : " "));
  1081. if (this.length > targetLength) {
  1082. return String(this);
  1083. } else {
  1084. targetLength = targetLength-this.length;
  1085. if (targetLength > padString.length) {
  1086. padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
  1087. }
  1088. return String(this) + padString.slice(0, targetLength);
  1089. }
  1090. };
  1091. }