{ "version": 3, "sources": ["../../../../../../../../source/febe/ui/src/scripts/classes/Request.ts", "../../../../../../../../source/febe/ui/static/svg/icons/tune.svg", "../../../../../../../../source/febe/ui/static/svg/icons/rabbit_running.svg", "../../../../../../../../source/febe/ui/static/svg/icons/rabbit.svg", "../../../../../../../../source/febe/ui/static/svg/icons/schedule.svg", "../../../../../../../../source/febe/ui/src/scripts/classes/Utils.ts"], "sourcesContent": ["/*\n\n Usage example:\n\n const req = new Request({\n url: String [false], // => url to call\n async: Boolean [true], =>\n readystate: Function [Function]\n }).fetch().then(response => {\n // do something with the response\n }).catch(err => {\n // handle request errors\n });\n\n*/\nlet parseResponse = (s: string) => {\n try {\n return JSON.parse(s);\n } catch {\n return s;\n }\n};\n\n// transforms an object into FormData\nlet formify = (data = {}, fd = false) => {\n if (fd === false) {\n let e = [];\n for (let key in data) e.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);\n return e.join(\"&\").replace(/%20/g, \"+\");\n } else {\n // parse form data object\n let d = new FormData();\n for (let key in data) {\n if (data[key] instanceof Array) {\n data[key].forEach((v: string) => d.append(key, v));\n } else {\n d.append(key, data[key]);\n }\n }\n return d;\n }\n};\n\nexport default class Request {\n props: {\n url: string;\n withCredentials: boolean;\n contentType: string;\n async: boolean;\n parseFormData: boolean;\n };\n\n constructor(props = {}) {\n this.props = {\n url: \"/\",\n withCredentials: false,\n contentType: \"application/json\",\n async: true,\n parseFormData: false,\n ...props,\n };\n }\n\n // Request.fetch will deprecate in favor of Request.get\n fetch() {\n return this.get();\n }\n\n // creates a GET request\n get() {\n let { url, withCredentials, async } = this.props;\n\n return new Promise((resolve, reject) => {\n let ajax = new XMLHttpRequest();\n ajax.onreadystatechange = function () {\n if (this.readyState == 4) {\n const res = parseResponse(this.responseText);\n switch (this.status) {\n case 404:\n case 500:\n return reject(res);\n case 200:\n default:\n return resolve(res);\n }\n }\n };\n\n ajax.open(\"GET\", url, async);\n ajax.withCredentials = withCredentials;\n ajax.send();\n });\n }\n\n // creates a POST request\n post(data: any, string?: boolean) {\n return new Promise((resolve, reject) => {\n let { url, contentType, parseFormData } = this.props;\n\n const xhr = new XMLHttpRequest();\n\n xhr.onreadystatechange = function () {\n if (this.readyState === 4) {\n const res = parseResponse(this.responseText);\n switch (this.status) {\n case 404:\n case 500:\n return reject(res);\n case 200:\n default:\n return resolve(res);\n }\n }\n };\n\n xhr.open(\"POST\", url, true);\n xhr.setRequestHeader(\"Content-Type\", contentType);\n\n xhr.setRequestHeader(\"X-CSRF-Token\", globalThis.csrf_token);\n\n xhr.withCredentials = true;\n\n if (contentType == \"application/x-www-form-urlencoded\") {\n data = formify(data, parseFormData);\n } else if (string === true) {\n data = JSON.stringify(data);\n }\n xhr.send(data);\n });\n }\n\n // creates a PUT request\n update(data: any, string: boolean) {\n return new Promise((resolve, reject) => {\n let { url, contentType, parseFormData } = this.props;\n\n const xhr = new XMLHttpRequest();\n\n xhr.onreadystatechange = function () {\n if (this.readyState === 4) {\n const res = parseResponse(this.responseText);\n switch (this.status) {\n case 404:\n case 500:\n return reject(res);\n case 200:\n default:\n return resolve(res);\n }\n }\n };\n xhr.open(\"PUT\", url, true);\n xhr.setRequestHeader(\"Content-Type\", contentType);\n xhr.setRequestHeader(\"X-CSRF-Token\", globalThis.csrf_token);\n xhr.withCredentials = true;\n\n if (contentType == \"application/x-www-form-urlencoded\") {\n data = formify(data, parseFormData);\n } else if (string === true) {\n data = JSON.stringify(data);\n }\n\n xhr.send(data);\n });\n }\n\n // creates a DELETE request\n delete() {\n return new Promise((resolve, reject) => {\n let { url } = this.props;\n\n const xhr = new XMLHttpRequest();\n\n xhr.onreadystatechange = function () {\n if (this.readyState === 4) {\n switch (this.status) {\n case 404:\n case 500:\n return reject();\n case 200:\n default:\n return resolve(null);\n }\n }\n };\n\n xhr.open(\"DELETE\", url, true);\n xhr.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded\");\n xhr.withCredentials = true;\n xhr.setRequestHeader(\"X-CSRF-Token\", globalThis.csrf_token);\n\n xhr.send();\n\n resolve(null);\n });\n }\n}\n", "", "", "", "", "import tune from \"@icons/tune.svg\";\nimport rabbitRunning from \"@icons/rabbit_running.svg\";\nimport rabbit from \"@icons/rabbit.svg\";\nimport schedule from \"@icons/schedule.svg\";\nimport check from \"@icons/check.svg\";\nimport warning from \"@icons/warning.svg\";\nimport close from \"@icons/close.svg\";\n\nimport Request from \"./Request\";\nimport { Hatch, SafeString } from \"./Hatch\";\nimport { FetchFormModal } from \"../components/Modal\";\nimport { ToggleableElement } from \"../global/interfaces\";\n\ninterface cc {\n code: string;\n name: string;\n}\n\n/*\n Simplified delay mechanism as a promise\n */\nexport const wait = (time = 0) => {\n return new Promise((resolve) => setTimeout(() => resolve(null), time));\n};\n\ninterface toast {\n id?: string;\n type?: string;\n message: SafeString | string;\n title?: SafeString | string;\n action?: {\n type: string;\n label: string;\n href: string;\n };\n}\n\n// core methods and utilities should be defined as a method on this class.\nexport default class Utils {\n /*\n converts a size in bytes to a 'human' format\n */\n static humanByteSize = (bytes: number, si: number) => {\n const thresh = si ? 1000 : 1024;\n if (Math.abs(bytes) < thresh) {\n return {\n string: bytes + \" B\",\n };\n }\n let units = si\n ? [\"kB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\", \"ZB\", \"YB\"]\n : [\"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\", \"ZiB\", \"YiB\"];\n let u = -1;\n do {\n bytes /= thresh;\n ++u;\n } while (Math.abs(bytes) >= thresh && u < units.length - 1);\n return {\n value: parseFloat(bytes.toFixed(1)),\n units: units[u],\n string: `${bytes.toFixed(1)}${units[u]}`,\n };\n };\n\n /*\n Copies input content to the OS clipboard\n */\n static copyToClipboard = (el: Element) => {\n navigator.clipboard.writeText(el.getAttribute(\"data-clipboard\")).then(() => {\n el.setAttribute(\"data-label-cached\", el.innerHTML);\n el.innerHTML = `\n
`;\n if (el.classList.contains(\"primary\")) {\n el.classList.remove(\"primary\");\n el.classList.add(\"secondary\");\n } else {\n el.classList.add(\"primary\");\n el.classList.remove(\"secondary\");\n }\n setTimeout(() => {\n el.innerHTML = el.getAttribute(\"data-label-cached\");\n if (el.classList.contains(\"secondary\")) {\n el.classList.add(\"primary\");\n el.classList.remove(\"secondary\");\n } else {\n el.classList.remove(\"primary\");\n el.classList.add(\"secondary\");\n }\n }, 500);\n });\n };\n\n /*\n Show all items with a hidden class in container\n */\n static toggleShowMore = (el: ToggleableElement) => {\n if (!el.toggled) {\n el.toggled = true;\n el.innerText = \"Show less\";\n el.parentElement.parentElement.querySelectorAll(\".hidden:not(textarea)\").forEach((item) => {\n item.classList.add(\"toToggle\");\n item.classList.remove(\"hidden\");\n });\n } else {\n el.toggled = false;\n el.innerText = \"Show more\";\n el.parentElement.parentElement.querySelectorAll(\".toToggle\").forEach((item) => {\n item.classList.remove(\"toToggle\");\n item.classList.add(\"hidden\");\n });\n }\n };\n\n static defang = (url: string): string => {\n url = url.replace(\"https://\", \"hxxps://\");\n url = url.replace(\"http://\", \"hxxp://\");\n url = url.replace(\"ftp://\", \"fxp://\");\n url = url.replaceAll(\".\", \"[.]\");\n return url;\n };\n\n static refang = (url: string): string => {\n url = url.replace(\"hxxps://\", \"https://\");\n url = url.replace(\"hxxp://\", \"http://\");\n url = url.replace(\"fxp://\", \"ftp://\");\n url = url.replaceAll(\"[.]\", \".\");\n return url;\n };\n\n /*\n Checks if element is overflow and direction\n {\n isOverflow: true | false\n dir: \"both\" | \"left\" | \"right\"\n }\n */\n static getOverflow = (el: Element) => {\n let direction: string;\n if (el.scrollLeft == 0) {\n direction = \"left\";\n } else if (Math.round(el.scrollWidth) - 1 <= Math.round(el.getBoundingClientRect().width + el.scrollLeft)) {\n direction = \"right\";\n } else if (el.scrollWidth > el.clientWidth) {\n direction = \"both\";\n } else {\n direction = null;\n }\n\n let overflow = {\n isOverflow: el.scrollWidth > el.clientWidth,\n dir: direction,\n };\n return overflow;\n };\n\n static scrollAndBlink = async (el: HTMLElement) => {\n el.scrollIntoView({ behavior: \"smooth\", block: \"center\", inline: \"nearest\" });\n\n for (let i = 0; i < 12; i++) {\n await wait(300);\n el.classList.toggle(\"blink\");\n }\n };\n\n /*\n Returns the color label for scores\n */\n static getSeverity = (score: number) => {\n let statusColor = \"normal\";\n\n if (score > 4) {\n statusColor = \"warning\";\n }\n if (score > 7) {\n statusColor = \"alert\";\n }\n\n return statusColor;\n };\n\n /*\n Simple form validation\n */\n\n static validateForm = (form: HTMLFormElement) => {\n let requireds = form.querySelectorAll(\"[required]\");\n let valid = true;\n requireds.forEach((item: HTMLInputElement) => {\n if (!item.value) {\n valid = false;\n }\n });\n if (valid) {\n return true;\n } else {\n globalThis.notification.notify({\n title: \"Failed to send\",\n message: \"Please fill out the required field(s).\",\n type: \"danger\",\n dismiss: \"Dismiss\",\n timer: 5000,\n });\n return false;\n }\n };\n\n /*\n Returns a new Request class\n - for documentation, see file ./classes/Request.es6\n - usage: Utilities.Request({...}) => Promise\n */\n static Request = (props: any) => {\n return new Request(props);\n };\n\n static Hash = (str = \"\") => {\n let hash = 0;\n if (str.length == 0) return hash;\n for (let i = 0; i < str.length; i++) {\n let char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32bit integer\n }\n return hash;\n };\n\n static stateToIcon = (state: string) => {\n let iconTemplate = (str: string) => {\n return ` `;\n };\n let statusMap = {\n static_analysis: {\n icon: iconTemplate(tune),\n label: \"Scheduler\",\n },\n running: {\n icon: iconTemplate(rabbitRunning),\n label: \"Running\",\n },\n reported: {\n icon: iconTemplate(check),\n label: \"Reported\",\n },\n scheduled: {\n icon: iconTemplate(rabbit),\n label: \"Scheduled\",\n },\n pending: {\n icon: iconTemplate(schedule),\n label: \"Pending\",\n },\n failed: {\n icon: iconTemplate(warning),\n label: \"Failed\",\n },\n };\n\n return statusMap[state];\n };\n\n static spawnToast = (toast: toast) => {\n let holder = document.querySelector(\"#toasts\");\n let el = document.createElement(\"div\");\n if (toast.id) el.setAttribute(\"ID\", toast.id);\n el.classList.add(\"toast\");\n el.classList.add(toast.type ? toast.type : \"info\");\n el.innerHTML = Hatch`\n${toast.message}
\n ${\n toast.action\n ? Hatch`\n \n `\n : \"\"\n }\n `.toString();\n let closeEl = el.querySelector(\"[data-close-toast]\");\n closeEl?.addEventListener(\"click\", (e: Event) => {\n e.preventDefault();\n this.closeToast(el);\n });\n holder?.appendChild(el);\n };\n\n static formatTags = (tags: string[]) => {\n if (!tags || !tags.length) {\n return \"\";\n }\n let formatted = \"\";\n tags.forEach((tag) => {\n let prefix = tag.split(\":\")[0];\n let content = tag;\n let rhs = tag.split(\":\")[1];\n if (rhs) {\n content = rhs;\n }\n let tagClass = (() => {\n switch (prefix) {\n case \"botnet\":\n return \"blue\";\n case \"campaign\":\n return \"purple\";\n case \"family\":\n return \"rose\";\n case \"brand\":\n return \"turquoise\";\n default:\n return \"\";\n }\n })();\n\n formatted += Hatch`${content}`;\n });\n return formatted;\n };\n\n static ccToLabel = async () => {\n let response = await fetch(location.origin + \"/static/json/country-codes.json\");\n return (await response.json()) as { code: string; name: string }[];\n };\n\n static closeToast = (toast: HTMLElement) => {\n toast.classList.add(\"out\");\n toast.style.marginTop = `-${toast.getBoundingClientRect().height}px`;\n toast.style.opacity = \"0\";\n setTimeout(() => {\n toast?.remove();\n }, 400);\n };\n\n static feedback = () => {\n let feedbackform = Hatch`\nPlease let us know if you have any feedback for us. For example, if you are missing support for a specific malware family, you believe the scoring to be inaccurate, or if there is something you like/dislike in general.
\n \n \n \n \n \n \n \n `;\n\n new FetchFormModal({\n body: feedbackform,\n action: \"/feedback\",\n successMsg: \"Thank you, your feedback has been successfully submitted.\",\n });\n };\n}\n\nexport const debounce = (delay: number, fun: (...args: any[]) => any) => {\n let timeout: number;\n return (...args: any[]) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => {\n fun(...args);\n }, delay);\n };\n};\n\nexport const deepEqual = (lhs: Record