2020-09-14 08:33:59 +02:00
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
// Function called to setup custom MarkdownIt rendering and parsing rules
|
2020-10-07 11:13:30 +02:00
|
|
|
|
var markdownItCustomPostInit = markdownItCustomPostInit
|
2020-09-14 08:33:59 +02:00
|
|
|
|
// Hint option passed to the CodeMirror constructor
|
|
|
|
|
var cmCustomHints = hintMISPElements
|
|
|
|
|
// Setup function called after the CodeMirror initialization
|
2020-10-15 11:56:21 +02:00
|
|
|
|
var cmCustomSetup = cmCustomSetup
|
2020-09-14 08:33:59 +02:00
|
|
|
|
// Hook allowing to alter the raw text before returning the GFM version to the user to be downloaded
|
|
|
|
|
var markdownGFMSubstitution = replaceMISPElementByTheirValue
|
|
|
|
|
// Post rendering hook called after the markdown is displayed, allowing to register listener
|
|
|
|
|
var markdownCustomPostRenderingListener = setupMISPElementMarkdownListeners
|
|
|
|
|
// Post rendering hook called after the markdown is display, allowing to perform any actions on the rendered markdown
|
|
|
|
|
var markdownCustomPostRenderingActions = attachRemoteMISPElements
|
|
|
|
|
// CodeMirror replacement/insertion actions that can be executed on the editor's text
|
|
|
|
|
var customReplacementActions = MISPElementReplacementActions
|
|
|
|
|
// Called after CodeMirror initialization to insert custom top bar buttons
|
|
|
|
|
var insertCustomToolbarButtons = insertMISPElementToolbarButtons
|
|
|
|
|
|
|
|
|
|
// Key of the model used by the form when saving
|
|
|
|
|
var modelNameForSave = 'EventReport';
|
|
|
|
|
// Key of the field used by the form when saving
|
|
|
|
|
var markdownModelFieldNameForSave = 'content';
|
|
|
|
|
|
2020-10-05 11:21:51 +02:00
|
|
|
|
var dotTemplateAttribute = doT.template("<span class=\"misp-element-wrapper attribute\" data-scope=\"{{=it.scope}}\" data-elementid=\"{{=it.elementid}}\"><span class=\"bold\"><span class=\"attr-type\"><span>{{=it.type}}</span></span><span class=\"blue\"><span class=\"attr-value\"><span>{{=it.value}}</span></span></span></span></span>");
|
|
|
|
|
var dotTemplateAttributePicture = doT.template("<div class=\"misp-picture-wrapper attributePicture\"><img data-scope=\"{{=it.scope}}\" data-elementid=\"{{=it.elementid}}\" href=\"#\" src=\"{{=it.src}}\" alt=\"{{=it.alt}}\" title=\"\"/></div>");
|
2020-09-29 12:16:50 +02:00
|
|
|
|
var dotTemplateGalaxyMatrix = doT.template("<div class=\"misp-picture-wrapper embeddedGalaxyMatrix\" data-scope=\"{{=it.scope}}\" data-elementid=\"{{=it.elementid}}\" data-eventid=\"{{=it.eventid}}\"></div>");
|
2020-10-05 11:21:51 +02:00
|
|
|
|
var dotTemplateTag = doT.template("<span class=\"tag misp-tag-wrapper embeddedTag\" data-scope=\"{{=it.scope}}\" data-elementid=\"{{!it.elementid}}\" data-eventid=\"{{=it.eventid}}\">{{=it.elementid}}</span>");
|
|
|
|
|
var dotTemplateObject = doT.template("<span class=\"misp-element-wrapper object\" data-scope=\"{{=it.scope}}\" data-elementid=\"{{=it.elementid}}\"><span class=\"bold\"><span class=\"obj-type\"><span>{{=it.type}}</span></span><span class=\"obj-value\"><span>{{=it.value}}</span></span></span></span>");
|
2020-10-05 14:07:51 +02:00
|
|
|
|
var dotTemplateObjectAttribute = doT.template("<span class=\"misp-element-wrapper object\" data-scope=\"{{=it.scope}}\" data-elementid=\"{{=it.elementid}}\"><span class=\"bold\"><span class=\"obj-type\"><span class=\"object-name\">{{=it.objectname}}</span>↦ <span class=\"object-attribute-type\">{{=it.type}}</span></span><span class=\"obj-value\"><span>{{=it.value}}</span></span></span></span>");
|
2020-09-14 08:33:59 +02:00
|
|
|
|
var dotTemplateInvalid = doT.template("<span class=\"misp-element-wrapper invalid\"><span class=\"bold red\">{{=it.scope}}<span class=\"blue\"> ({{=it.id}})</span></span></span>");
|
2020-10-01 16:55:17 +02:00
|
|
|
|
var dotCloseButtonTemplate = doT.template('<button type="button" class="close" style="margin-left: 5px;" data-scope=\"{{=it.scope}}\" data-elementid=\"{{!it.elementID}}\" onclick="closeThePopover(this)">×</button>');
|
2020-10-06 10:04:13 +02:00
|
|
|
|
var dotTemplateRenderingDisabled = doT.template("<span class=\"misp-element-wrapper attribute\" data-scope=\"{{=it.scope}}\" data-elementid=\"{{!it.elementid}}\" data-eventid=\"{{=it.eventid}}\">{{=it.value}}</span>");
|
2020-10-15 11:56:21 +02:00
|
|
|
|
var dotTemplateSuggestionAttribute = doT.template("<span class=\"misp-element-wrapper suggestion attribute\" data-scope=\"{{=it.scope}}\" data-indexstart=\"{{=it.indexStart}}\" data-elementid=\"{{=it.elementid}}\" data-suggestionkey=\"{{=it.suggestionkey}}\"><span class=\"bold\"><span class=\"attr-type\"><input type=\"checkbox\" {{? it.checked }}checked=\"checked\"{{?}}></input><span>{{=it.type}}</span></span><span class=\"blue\"><span class=\"attr-value\"><span>{{=it.value}}</span></span></span></span></span>");
|
2020-10-06 10:04:13 +02:00
|
|
|
|
|
|
|
|
|
var renderingRules = {
|
|
|
|
|
'attribute': true,
|
|
|
|
|
'attribute-picture': true,
|
|
|
|
|
'object': true,
|
|
|
|
|
'object-attribute': true,
|
|
|
|
|
'tag': true,
|
|
|
|
|
'galaxymatrix': true,
|
2020-10-15 11:56:21 +02:00
|
|
|
|
'suggestion': true,
|
2020-10-06 10:04:13 +02:00
|
|
|
|
}
|
2020-10-01 16:55:17 +02:00
|
|
|
|
var galaxyMatrixTimer, tagTimers = {};
|
|
|
|
|
var cache_matrix = {}, cache_tag = {};
|
2020-10-18 21:10:45 +02:00
|
|
|
|
var firstCustomPostRenderCall = true;
|
2020-10-15 11:56:21 +02:00
|
|
|
|
var contentBeforeSuggestions
|
|
|
|
|
var typeToCategoryMapping
|
|
|
|
|
var entitiesFromComplexTool
|
|
|
|
|
var $suggestionContainer
|
2020-10-19 18:12:41 +02:00
|
|
|
|
var unreferencedElements = {
|
|
|
|
|
values: null,
|
|
|
|
|
context: null
|
|
|
|
|
};
|
2020-10-21 11:28:44 +02:00
|
|
|
|
var suggestionIDs = []
|
2020-10-15 11:56:21 +02:00
|
|
|
|
var suggestions = {}
|
2020-10-19 18:12:41 +02:00
|
|
|
|
var pickedSuggestion = { tableID: null, tr: null, entity: null, index: null, isContext: null }
|
2020-09-14 08:33:59 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
_____ _ __ __ _
|
|
|
|
|
/ ____| | | | \/ (_)
|
|
|
|
|
| | ___ __| | ___| \ / |_ _ __ _ __ ___ _ __
|
|
|
|
|
| | / _ \ / _` |/ _ \ |\/| | | '__| '__/ _ \| '__|
|
|
|
|
|
| |___| (_) | (_| | __/ | | | | | | | | (_) | |
|
|
|
|
|
\_____\___/ \__,_|\___|_| |_|_|_| |_| \___/|_|
|
|
|
|
|
*/
|
|
|
|
|
|
2020-10-15 11:56:21 +02:00
|
|
|
|
function cmCustomSetup() {
|
|
|
|
|
$suggestionContainer = $('<div/>').attr('id', 'suggestion-container').addClass('hidden')
|
|
|
|
|
$suggestionContainer.insertAfter('#editor-subcontainer')
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-14 08:33:59 +02:00
|
|
|
|
/* Replacement actions and Toolbar addition */
|
|
|
|
|
function MISPElementReplacementActions(action) {
|
|
|
|
|
var start = cm.getCursor('start')
|
|
|
|
|
var end = cm.getCursor('end')
|
|
|
|
|
var content = cm.getRange(start, end)
|
|
|
|
|
var replacement = content
|
|
|
|
|
var setCursorTo = false
|
|
|
|
|
var noMatch = false
|
|
|
|
|
|
|
|
|
|
switch (action) {
|
|
|
|
|
case 'element':
|
|
|
|
|
replacement = '@[MISPElement]()'
|
|
|
|
|
end = null
|
|
|
|
|
cm.replaceRange(replacement, start)
|
|
|
|
|
cm.setSelection({line: start.line, ch: start.ch + 2}, {line: start.line, ch: start.ch + 2 + 11})
|
|
|
|
|
cm.focus()
|
|
|
|
|
return true;
|
|
|
|
|
case 'attribute':
|
|
|
|
|
replacement = '@[attribute]()'
|
|
|
|
|
end = null
|
|
|
|
|
setCursorTo = {line: start.line, ch: start.ch + replacement.length - 1}
|
|
|
|
|
break;
|
|
|
|
|
case 'attribute-attachment':
|
|
|
|
|
replacement = '@![attribute]()'
|
|
|
|
|
end = null
|
|
|
|
|
setCursorTo = {line: start.line, ch: start.ch + replacement.length - 1}
|
|
|
|
|
break;
|
|
|
|
|
case 'object':
|
|
|
|
|
replacement = '@[object]()'
|
|
|
|
|
end = null
|
|
|
|
|
setCursorTo = {line: start.line, ch: start.ch + replacement.length - 1}
|
|
|
|
|
break;
|
2020-10-01 16:55:17 +02:00
|
|
|
|
case 'tag':
|
|
|
|
|
replacement = '@[tag]()'
|
|
|
|
|
end = null
|
|
|
|
|
setCursorTo = {line: start.line, ch: start.ch + replacement.length - 1}
|
|
|
|
|
break;
|
2020-10-05 11:38:30 +02:00
|
|
|
|
case 'galaxy-matrix':
|
2020-09-29 12:16:50 +02:00
|
|
|
|
replacement = '@[galaxymatrix]()'
|
|
|
|
|
end = null
|
|
|
|
|
setCursorTo = {line: start.line, ch: start.ch + replacement.length - 1}
|
|
|
|
|
break;
|
2020-09-14 08:33:59 +02:00
|
|
|
|
default:
|
|
|
|
|
noMatch = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (noMatch) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
cm.replaceRange(replacement, start, end)
|
|
|
|
|
if (setCursorTo !== false) {
|
|
|
|
|
cm.setCursor(setCursorTo.line, setCursorTo.ch)
|
|
|
|
|
}
|
|
|
|
|
cm.focus()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function insertMISPElementToolbarButtons() {
|
|
|
|
|
insertTopToolbarSection()
|
2020-10-06 10:04:13 +02:00
|
|
|
|
insertTopToolbarButton('cube', 'attribute', 'Attribute')
|
|
|
|
|
insertTopToolbarButton('cubes', 'object', 'Object')
|
|
|
|
|
insertTopToolbarButton('image', 'attribute-attachment', 'Attribute picture')
|
|
|
|
|
insertTopToolbarButton('tag', 'tag', 'Tag')
|
|
|
|
|
insertTopToolbarButton('atlas', 'galaxy-matrix', 'Galaxy matrix')
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Hints */
|
2020-10-02 10:48:52 +02:00
|
|
|
|
var MISPElementHints = {}
|
2020-09-14 08:33:59 +02:00
|
|
|
|
function buildMISPElementHints() {
|
2020-10-02 10:48:52 +02:00
|
|
|
|
MISPElementHints['attribute'] = []
|
|
|
|
|
Object.keys(proxyMISPElements['attribute']).forEach(function(uuid) {
|
|
|
|
|
var attribute = proxyMISPElements['attribute'][uuid]
|
|
|
|
|
MISPElementHints['attribute'].push(
|
|
|
|
|
[attribute.value, uuid],
|
|
|
|
|
[attribute.type, uuid],
|
|
|
|
|
[attribute.id, uuid],
|
|
|
|
|
[attribute.uuid, uuid],
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
MISPElementHints['object'] = []
|
|
|
|
|
Object.keys(proxyMISPElements['object']).forEach(function(uuid) {
|
|
|
|
|
var object = proxyMISPElements['object'][uuid]
|
2021-05-04 11:01:04 +02:00
|
|
|
|
var topPriorityValue = getTopPriorityValue(object)
|
2020-10-02 10:48:52 +02:00
|
|
|
|
MISPElementHints['object'].push(
|
|
|
|
|
[object.name, uuid],
|
|
|
|
|
[object.id, uuid],
|
|
|
|
|
[object.uuid, uuid],
|
2021-05-04 11:01:04 +02:00
|
|
|
|
[topPriorityValue, uuid],
|
2020-10-02 10:48:52 +02:00
|
|
|
|
)
|
2020-09-14 08:33:59 +02:00
|
|
|
|
})
|
2020-10-02 10:48:52 +02:00
|
|
|
|
MISPElementHints['galaxymatrix'] = []
|
|
|
|
|
Object.keys(proxyMISPElements['galaxymatrix']).forEach(function(uuid) {
|
|
|
|
|
var galaxy = proxyMISPElements['galaxymatrix'][uuid]
|
|
|
|
|
MISPElementHints['galaxymatrix'].push(
|
|
|
|
|
[galaxy.id, uuid],
|
|
|
|
|
[galaxy.uuid, uuid],
|
|
|
|
|
[galaxy.name, uuid],
|
|
|
|
|
[galaxy.namespace, uuid],
|
|
|
|
|
[galaxy.type, uuid],
|
|
|
|
|
)
|
2020-09-29 12:16:50 +02:00
|
|
|
|
})
|
2020-10-02 10:48:52 +02:00
|
|
|
|
MISPElementHints['tag'] = []
|
|
|
|
|
Object.keys(proxyMISPElements['tagname']).forEach(function(tagName) {
|
|
|
|
|
var tag = proxyMISPElements['tagname'][tagName]
|
|
|
|
|
MISPElementHints['tag'].push([tagName, tagName])
|
2020-09-14 08:33:59 +02:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function hintMISPElements(cm, options) {
|
2020-10-02 10:48:52 +02:00
|
|
|
|
var authorizedMISPElements = ['attribute', 'object', 'galaxymatrix', 'tag']
|
2020-10-01 17:06:56 +02:00
|
|
|
|
var availableScopes = ['attribute', 'object', 'galaxymatrix', 'tag']
|
2020-09-29 12:16:50 +02:00
|
|
|
|
var reMISPElement = RegExp('@\\[(?<scope>' + authorizedMISPElements.join('|') + ')\\]\\((?<elementid>[^\\)]+)?\\)');
|
2021-05-04 11:01:04 +02:00
|
|
|
|
var reMISPScope = RegExp('@\\[(?<scope>\\S+)\\]\\(\\)');
|
2020-09-14 08:33:59 +02:00
|
|
|
|
var reExtendedWord = /\S/
|
2020-10-02 10:48:52 +02:00
|
|
|
|
var hintList = []
|
2020-09-14 08:33:59 +02:00
|
|
|
|
var scope, elementID, element
|
|
|
|
|
var cursor = cm.getCursor()
|
|
|
|
|
var line = cm.getLine(cursor.line)
|
|
|
|
|
var start = cursor.ch
|
|
|
|
|
var end = cursor.ch
|
|
|
|
|
while (start && reExtendedWord.test(line.charAt(start - 1))) --start
|
|
|
|
|
while (end < line.length && reExtendedWord.test(line.charAt(end))) ++end
|
|
|
|
|
var word = line.slice(start, end).toLowerCase()
|
|
|
|
|
|
2020-09-29 12:26:20 +02:00
|
|
|
|
if (word === '@[]()') {
|
|
|
|
|
availableScopes.forEach(function(scope) {
|
|
|
|
|
hintList.push({
|
|
|
|
|
text: '@[' + scope + ']()'
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
return {
|
|
|
|
|
list: hintList,
|
|
|
|
|
from: CodeMirror.Pos(cursor.line, start),
|
|
|
|
|
to: CodeMirror.Pos(cursor.line, end)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-04 11:01:04 +02:00
|
|
|
|
var resScope = reMISPScope.exec(word)
|
|
|
|
|
if (resScope !== null) {
|
|
|
|
|
var partialScope = resScope.groups.scope
|
|
|
|
|
availableScopes.forEach(function(scope) {
|
|
|
|
|
if (scope.startsWith(partialScope) && scope !== partialScope) {
|
|
|
|
|
hintList.push({
|
|
|
|
|
text: '@[' + scope + ']()'
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
if (hintList.length > 0) {
|
|
|
|
|
return {
|
|
|
|
|
list: hintList,
|
|
|
|
|
from: CodeMirror.Pos(cursor.line, start),
|
|
|
|
|
to: CodeMirror.Pos(cursor.line, end)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-14 08:33:59 +02:00
|
|
|
|
var res = reMISPElement.exec(word)
|
|
|
|
|
if (res !== null) {
|
|
|
|
|
scope = res.groups.scope
|
2020-09-29 12:16:50 +02:00
|
|
|
|
elementID = res.groups.elementid !== undefined ? res.groups.elementid : ''
|
2020-10-02 10:48:52 +02:00
|
|
|
|
if (scope === 'tag') {
|
|
|
|
|
element = proxyMISPElements['tagname'][elementID]
|
|
|
|
|
} else {
|
|
|
|
|
element = proxyMISPElements[scope][elementID]
|
|
|
|
|
}
|
2020-09-14 08:33:59 +02:00
|
|
|
|
if (element !== undefined) {
|
|
|
|
|
hintList.push(
|
|
|
|
|
{
|
2020-10-01 11:08:30 +02:00
|
|
|
|
text: '@[' + scope + '](' + element.uuid + ')',
|
2020-09-14 08:33:59 +02:00
|
|
|
|
render: function(elem, self, data) {
|
|
|
|
|
var hintElement = renderHintElement(scope, element)
|
|
|
|
|
$(elem).append(hintElement)
|
|
|
|
|
},
|
|
|
|
|
className: 'hint-container',
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
} else { // search in hint arrays
|
2020-09-29 12:16:50 +02:00
|
|
|
|
var addedItems = {}
|
2020-10-29 18:20:58 +01:00
|
|
|
|
var maxHints = 20 + 10*(elementID.length - 3 >= 0 ? elementID.length - 3 : 0); // adapt hint numbers if typed value is large enough
|
2020-10-02 10:48:52 +02:00
|
|
|
|
if (MISPElementHints[scope] !== undefined) {
|
|
|
|
|
for (var i = 0; i < MISPElementHints[scope].length; i++) {
|
|
|
|
|
var hintArray = MISPElementHints[scope][i];
|
|
|
|
|
var hintValue = hintArray[0]
|
|
|
|
|
var hintUUID = hintArray[1]
|
2020-09-14 08:33:59 +02:00
|
|
|
|
if (hintList.length >= maxHints) {
|
2020-10-02 10:48:52 +02:00
|
|
|
|
break
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
2020-10-02 15:21:22 +02:00
|
|
|
|
if (hintValue.includes(elementID)) {
|
2020-10-02 10:48:52 +02:00
|
|
|
|
if (addedItems[hintUUID] === undefined) {
|
|
|
|
|
if (scope === 'tag') {
|
|
|
|
|
element = proxyMISPElements['tagname'][hintUUID]
|
|
|
|
|
element.uuid = hintUUID
|
|
|
|
|
} else {
|
|
|
|
|
element = proxyMISPElements[scope][hintUUID]
|
2020-09-29 12:16:50 +02:00
|
|
|
|
}
|
2020-10-02 10:48:52 +02:00
|
|
|
|
if (element !== undefined) {
|
|
|
|
|
hintList.push({
|
|
|
|
|
text: '@[' + scope + '](' + element.uuid + ')',
|
|
|
|
|
element: element,
|
|
|
|
|
render: function(elem, self, data) {
|
|
|
|
|
var hintElement = renderHintElement(scope, data.element)
|
|
|
|
|
$(elem).append(hintElement)
|
|
|
|
|
},
|
|
|
|
|
className: 'hint-container',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
addedItems[hintUUID] = true
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-02 10:48:52 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
list: hintList,
|
|
|
|
|
from: CodeMirror.Pos(cursor.line, start),
|
|
|
|
|
to: CodeMirror.Pos(cursor.line, end)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderHintElement(scope, element) {
|
|
|
|
|
var $node;
|
|
|
|
|
if (scope == 'attribute') {
|
|
|
|
|
$node = $('<span/>').addClass('hint-attribute')
|
2020-10-09 11:34:24 +02:00
|
|
|
|
if (isValidObjectAttribute(element)) {
|
|
|
|
|
$node.append($('<i/>').addClass('fas fa-cubes').css('margin-right', '3px'))
|
|
|
|
|
}
|
2020-10-02 11:22:51 +02:00
|
|
|
|
$node.append($('<i/>').addClass('').text('[' + element.category + '] '))
|
2020-09-14 08:33:59 +02:00
|
|
|
|
.append($('<span/>').addClass('bold').text(element.type + ' '))
|
2020-10-02 11:22:51 +02:00
|
|
|
|
.append(
|
|
|
|
|
$('<span/>').addClass('bold blue ellipsis-overflow')
|
|
|
|
|
.css({
|
|
|
|
|
'max-width': '500px',
|
|
|
|
|
'display': 'table-cell'
|
|
|
|
|
})
|
|
|
|
|
.text(element.value)
|
|
|
|
|
)
|
2020-09-14 08:33:59 +02:00
|
|
|
|
} else if (scope == 'object') {
|
2021-05-04 11:01:04 +02:00
|
|
|
|
var topPriorityValue = getTopPriorityValue(element)
|
2020-09-14 08:33:59 +02:00
|
|
|
|
$node = $('<span/>').addClass('hint-object')
|
2020-10-02 11:22:51 +02:00
|
|
|
|
$node.append($('<i/>').addClass('').text('[' + element['meta-category'] + '] '))
|
2020-09-14 08:33:59 +02:00
|
|
|
|
.append($('<span/>').addClass('bold').text(element.name + ' '))
|
2020-10-02 11:22:51 +02:00
|
|
|
|
.append(
|
|
|
|
|
$('<span/>').addClass('bold blue ellipsis-overflow')
|
|
|
|
|
.css({
|
|
|
|
|
'max-width': '500px',
|
|
|
|
|
'display': 'table-cell'
|
|
|
|
|
})
|
|
|
|
|
.text(topPriorityValue)
|
|
|
|
|
)
|
2020-09-29 12:16:50 +02:00
|
|
|
|
} else if (scope == 'galaxymatrix') {
|
2020-10-02 10:48:52 +02:00
|
|
|
|
$node = $('<span/>').addClass('hint-galaxymatrix')
|
2020-10-02 11:22:51 +02:00
|
|
|
|
$node.append($('<i/>').addClass('').text('[' + element.namespace + '] '))
|
2020-09-29 12:16:50 +02:00
|
|
|
|
.append($('<span/>').addClass('bold').text(element.type + ' '))
|
|
|
|
|
.append($('<span/>').addClass('bold blue').text(element.name))
|
2020-10-02 10:48:52 +02:00
|
|
|
|
} else if (scope == 'tag') {
|
|
|
|
|
$node = $('<span/>').addClass('hint-tag')
|
|
|
|
|
$node.append(constructTagHtml(element.name, element.colour, {'box-shadow': 'none'}))
|
2020-09-14 08:33:59 +02:00
|
|
|
|
} else {
|
|
|
|
|
$node = $('<span>No match</span>') // should not happen
|
|
|
|
|
}
|
|
|
|
|
return $node
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
__ __ _ _ _____ _
|
|
|
|
|
| \/ | | | | | |_ _| |
|
|
|
|
|
| \ / | __ _ _ __| | ____| | _____ ___ __ | | | |_
|
|
|
|
|
| |\/| |/ _` | '__| |/ / _` |/ _ \ \ /\ / / '_ \ | | | __|
|
|
|
|
|
| | | | (_| | | | < (_| | (_) \ V V /| | | |_| |_| |_
|
|
|
|
|
|_| |_|\__,_|_| |_|\_\__,_|\___/ \_/\_/ |_| |_|_____|\__|
|
|
|
|
|
*/
|
2020-10-07 11:13:30 +02:00
|
|
|
|
function markdownItCustomPostInit() {
|
|
|
|
|
markdownItSetupRules()
|
2020-10-08 17:00:32 +02:00
|
|
|
|
fetchProxyMISPElements(function() {
|
|
|
|
|
doRender()
|
|
|
|
|
})
|
2020-10-07 11:13:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-14 08:33:59 +02:00
|
|
|
|
function markdownItSetupRules() {
|
|
|
|
|
md.renderer.rules.MISPElement = MISPElementRenderer;
|
|
|
|
|
md.renderer.rules.MISPPictureElement = MISPPictureElementRenderer;
|
|
|
|
|
md.inline.ruler.push('MISP_element_rule', MISPElementRule);
|
2020-10-15 11:56:21 +02:00
|
|
|
|
md.core.ruler.push('MISP_element_suggestion_rule', MISPElementSuggestionRule);
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 11:56:21 +02:00
|
|
|
|
function MISPElementSuggestionRule(state) {
|
|
|
|
|
var blockTokens = state.tokens
|
|
|
|
|
var tokens, blockToken, currentToken
|
|
|
|
|
var indexOfAllLines, lineOffset, absoluteLine, relativeIndex
|
2021-05-21 10:01:15 +02:00
|
|
|
|
var tokenMap
|
2020-10-15 11:56:21 +02:00
|
|
|
|
var i, j, l
|
|
|
|
|
for (i = 0, l = blockTokens.length; i < l; i++) {
|
|
|
|
|
blockToken = blockTokens[i]
|
|
|
|
|
if (blockToken.type !== 'inline') {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tokens = blockToken.children;
|
|
|
|
|
for (j = 0; j < tokens.length; j++) {
|
|
|
|
|
currentToken = tokens[j];
|
|
|
|
|
if (currentToken.type !== 'MISPElement' && !currentToken.isSuggestion) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if (blockToken.indexOfAllLines === undefined) {
|
|
|
|
|
indexOfAllLines = new md.block.State(blockToken.content, md, {}, [])
|
|
|
|
|
blockToken.indexOfAllLines = indexOfAllLines
|
|
|
|
|
}
|
|
|
|
|
lineOffset = getLineNumInArrayList(currentToken.content.indexes.start, blockToken.indexOfAllLines.bMarks)
|
2021-05-21 10:01:15 +02:00
|
|
|
|
tokenMap = findBackClosestStartLine(blockTokens, i)
|
|
|
|
|
var absoluteLine = tokenMap[0] + lineOffset
|
2020-10-15 11:56:21 +02:00
|
|
|
|
var relativeIndex = currentToken.content.indexes.start - blockToken.indexOfAllLines.bMarks[lineOffset]
|
|
|
|
|
state.tokens[i].children[j].content.indexes.lineStart = absoluteLine
|
|
|
|
|
state.tokens[i].children[j].content.indexes.start = relativeIndex
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-14 08:33:59 +02:00
|
|
|
|
/* Parsing Rules */
|
|
|
|
|
function MISPElementRule(state, startLine, endLine, silent) {
|
|
|
|
|
var pos, start, labelStart, labelEnd, res, elementID, code, content, token, tokens, attrs, scope
|
|
|
|
|
var oldPos = state.pos,
|
|
|
|
|
max = state.posMax
|
|
|
|
|
|
|
|
|
|
if (state.src.charCodeAt(state.pos) !== 0x40/* @ */) { return false; }
|
|
|
|
|
if (state.src.charCodeAt(state.pos + 1) === 0x21/* ! */) {
|
|
|
|
|
if (state.src.charCodeAt(state.pos + 2) !== 0x5B/* [ */) { return false;}
|
|
|
|
|
} else {
|
|
|
|
|
if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var isPicture = state.src.charCodeAt(state.pos + 1) === 0x21/* ! */
|
|
|
|
|
|
|
|
|
|
if (isPicture) {
|
|
|
|
|
labelStart = state.pos + 3;
|
|
|
|
|
labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 2, false);
|
|
|
|
|
} else {
|
|
|
|
|
labelStart = state.pos + 2;
|
|
|
|
|
labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parser failed to find ']', so it's not a valid link
|
|
|
|
|
if (labelEnd < 0) { return false; }
|
|
|
|
|
scope = state.src.slice(labelStart, labelEnd)
|
|
|
|
|
|
|
|
|
|
pos = labelEnd + 1;
|
|
|
|
|
if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {
|
|
|
|
|
start = pos;
|
2020-10-15 11:56:21 +02:00
|
|
|
|
if (scope == 'tag' || scope == 'suggestion') { // tags may contain spaces
|
|
|
|
|
res = parseDestinationValue(state.src, pos, state.posMax);
|
2020-10-01 16:55:17 +02:00
|
|
|
|
} else {
|
|
|
|
|
res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax);
|
|
|
|
|
}
|
2020-09-14 08:33:59 +02:00
|
|
|
|
if (res.ok) {
|
2020-09-14 09:34:26 +02:00
|
|
|
|
// parseLinkDestination does not support trailing characters such as `.` after the link
|
|
|
|
|
// so we have to find the matching `)`
|
|
|
|
|
var destinationEnd = res.str.length - 1
|
|
|
|
|
var traillingCharNumber = 0
|
|
|
|
|
for (var i = res.str.length-1; i > 1; i--) {
|
|
|
|
|
var code = res.str.charCodeAt(i)
|
|
|
|
|
if (code === 0x29 /* ) */) {
|
|
|
|
|
destinationEnd = i
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
traillingCharNumber++
|
|
|
|
|
}
|
|
|
|
|
elementID = res.str.substring(1, destinationEnd);
|
|
|
|
|
pos = res.pos - 1 - traillingCharNumber;
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) {
|
|
|
|
|
state.pos = oldPos;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
pos++;
|
|
|
|
|
|
2020-10-15 11:56:21 +02:00
|
|
|
|
if (scope == 'tag' || scope == 'suggestion') {
|
2020-10-01 16:55:17 +02:00
|
|
|
|
var reTagName = /^[^\n)]+$/
|
|
|
|
|
if (!reTagName.test(elementID)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
var reUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/
|
|
|
|
|
if (!reUUID.test(elementID)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We found the end of the link, and know for a fact it's a valid link;
|
|
|
|
|
// so all that's left to do is to call tokenizer.
|
|
|
|
|
content = {
|
|
|
|
|
scope: scope,
|
|
|
|
|
elementID: elementID,
|
2020-10-15 11:56:21 +02:00
|
|
|
|
indexes: {
|
|
|
|
|
start: oldPos,
|
|
|
|
|
}
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
if (isPicture) {
|
|
|
|
|
token = state.push('MISPPictureElement', 'div', 0);
|
|
|
|
|
} else {
|
|
|
|
|
token = state.push('MISPElement', 'div', 0);
|
2020-10-21 11:28:44 +02:00
|
|
|
|
if (scope == 'suggestion') {
|
|
|
|
|
token.isSuggestion = true
|
|
|
|
|
content.indexes.suggestionID = consumeSuggestionID()
|
|
|
|
|
} else {
|
|
|
|
|
token.isSuggestion = false
|
|
|
|
|
}
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
2020-10-15 11:56:21 +02:00
|
|
|
|
|
2020-09-14 08:33:59 +02:00
|
|
|
|
token.children = tokens;
|
|
|
|
|
token.content = content;
|
|
|
|
|
|
|
|
|
|
state.pos = pos;
|
|
|
|
|
state.posMax = max;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Rendering rules */
|
|
|
|
|
function MISPElementRenderer(tokens, idx, options, env, slf) {
|
2020-10-15 11:56:21 +02:00
|
|
|
|
var allowedScope = ['attribute', 'object', 'galaxymatrix', 'tag', 'suggestion']
|
2020-09-14 08:33:59 +02:00
|
|
|
|
var token = tokens[idx];
|
|
|
|
|
var scope = token.content.scope
|
|
|
|
|
var elementID = token.content.elementID
|
2020-10-15 11:56:21 +02:00
|
|
|
|
var indexes = token.content.indexes
|
2020-09-14 08:33:59 +02:00
|
|
|
|
if (allowedScope.indexOf(scope) == -1) {
|
|
|
|
|
return renderInvalidMISPElement(scope, elementID);
|
|
|
|
|
}
|
2020-10-15 11:56:21 +02:00
|
|
|
|
return renderMISPElement(scope, elementID, indexes)
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function MISPPictureElementRenderer(tokens, idx, options, env, slf) {
|
|
|
|
|
var allowedScope = ['attribute']
|
|
|
|
|
var token = tokens[idx];
|
|
|
|
|
var scope = token.content.scope
|
|
|
|
|
var elementID = token.content.elementID
|
|
|
|
|
if (allowedScope.indexOf(scope) == -1) {
|
|
|
|
|
return renderInvalidMISPElement(scope, elementID);
|
|
|
|
|
}
|
|
|
|
|
return renderMISPPictureElement(scope, elementID)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 11:56:21 +02:00
|
|
|
|
function renderMISPElement(scope, elementID, indexes) {
|
2020-09-14 08:33:59 +02:00
|
|
|
|
var templateVariables
|
2020-10-15 11:56:21 +02:00
|
|
|
|
if (scope == 'suggestion') {
|
2020-10-23 22:07:13 +02:00
|
|
|
|
var suggestionKey = 'suggestion-' + String(indexes.suggestionID)
|
2020-10-15 11:56:21 +02:00
|
|
|
|
if (suggestions[elementID] !== undefined) {
|
|
|
|
|
var suggestion = suggestions[elementID][suggestionKey]
|
|
|
|
|
if (suggestion !== undefined) {
|
|
|
|
|
templateVariables = sanitizeObject({
|
|
|
|
|
scope: 'suggestion',
|
|
|
|
|
elementid: elementID,
|
|
|
|
|
eventid: eventid,
|
2020-10-15 17:01:53 +02:00
|
|
|
|
type: suggestion.complexTypeToolResult.picked_type,
|
2020-10-15 11:56:21 +02:00
|
|
|
|
origValue: elementID,
|
|
|
|
|
value: suggestion.complexTypeToolResult.value,
|
2020-12-19 12:43:24 +01:00
|
|
|
|
indexStart: indexes.start,
|
2020-10-15 11:56:21 +02:00
|
|
|
|
suggestionkey: suggestionKey,
|
|
|
|
|
checked: suggestion.checked
|
|
|
|
|
})
|
|
|
|
|
return renderTemplateBasedOnRenderingOptions(scope, dotTemplateSuggestionAttribute, templateVariables);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-08 17:00:32 +02:00
|
|
|
|
if (proxyMISPElements !== null) {
|
|
|
|
|
if (scope == 'attribute') {
|
|
|
|
|
var attribute = proxyMISPElements[scope][elementID]
|
|
|
|
|
if (attribute !== undefined) {
|
|
|
|
|
var templateToRender = dotTemplateAttribute
|
|
|
|
|
var attributeData = {
|
|
|
|
|
scope: 'attribute',
|
|
|
|
|
elementid: elementID,
|
|
|
|
|
type: attribute.type,
|
|
|
|
|
value: attribute.value
|
|
|
|
|
}
|
|
|
|
|
if (isValidObjectAttribute(attribute)) {
|
|
|
|
|
var mispObject = getObjectFromAttribute(attribute)
|
|
|
|
|
if (mispObject !== undefined) {
|
|
|
|
|
attributeData.type = attribute.object_relation
|
|
|
|
|
attributeData.objectname = mispObject.name
|
|
|
|
|
templateToRender = dotTemplateObjectAttribute
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
templateVariables = sanitizeObject(attributeData)
|
|
|
|
|
return renderTemplateBasedOnRenderingOptions(scope, templateToRender, templateVariables);
|
2020-10-05 14:07:51 +02:00
|
|
|
|
}
|
2020-10-08 17:00:32 +02:00
|
|
|
|
} else if (scope == 'object') {
|
|
|
|
|
var mispObject = proxyMISPElements[scope][elementID]
|
|
|
|
|
if (mispObject !== undefined) {
|
|
|
|
|
var associatedTemplate = mispObject.template_uuid + '.' + mispObject.template_version
|
|
|
|
|
var objectTemplate = proxyMISPElements['objectTemplates'][associatedTemplate]
|
|
|
|
|
var topPriorityValue = mispObject.Attribute.length
|
|
|
|
|
if (objectTemplate !== undefined) {
|
|
|
|
|
var temp = getPriorityValue(mispObject, objectTemplate)
|
|
|
|
|
topPriorityValue = temp !== false ? temp : topPriorityValue
|
2020-10-05 14:07:51 +02:00
|
|
|
|
}
|
2020-10-08 17:00:32 +02:00
|
|
|
|
templateVariables = sanitizeObject({
|
|
|
|
|
scope: 'object',
|
|
|
|
|
elementid: elementID,
|
|
|
|
|
type: mispObject.name,
|
|
|
|
|
value: topPriorityValue
|
|
|
|
|
})
|
|
|
|
|
return renderTemplateBasedOnRenderingOptions(scope, dotTemplateObject, templateVariables);
|
2020-10-05 14:07:51 +02:00
|
|
|
|
}
|
2020-10-08 17:00:32 +02:00
|
|
|
|
} else if (scope == 'tag') {
|
|
|
|
|
templateVariables = sanitizeObject({scope: 'tag', elementid: elementID, eventid: eventid, value: elementID})
|
|
|
|
|
return renderTemplateBasedOnRenderingOptions(scope, dotTemplateTag, templateVariables);
|
|
|
|
|
} else if (scope == 'galaxymatrix') {
|
|
|
|
|
templateVariables = sanitizeObject({scope: 'galaxymatrix', elementid: elementID, eventid: eventid, value: elementID})
|
|
|
|
|
return renderTemplateBasedOnRenderingOptions(scope, dotTemplateGalaxyMatrix, templateVariables);
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return renderInvalidMISPElement(scope, elementID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderMISPPictureElement(scope, elementID) {
|
2020-10-08 17:00:32 +02:00
|
|
|
|
if (proxyMISPElements !== null) {
|
|
|
|
|
var attribute = proxyMISPElements[scope][elementID]
|
|
|
|
|
if (attribute !== undefined) {
|
|
|
|
|
var templateVariables = sanitizeObject({
|
|
|
|
|
scope: 'attribute',
|
|
|
|
|
elementid: elementID,
|
|
|
|
|
type: attribute.type,
|
|
|
|
|
value: attribute.value,
|
|
|
|
|
alt: scope + ' ' + elementID,
|
|
|
|
|
src: baseurl + '/attributes/viewPicture/' + attribute.id,
|
|
|
|
|
title: attribute.type + ' ' + attribute.value,
|
|
|
|
|
})
|
|
|
|
|
return renderTemplateBasedOnRenderingOptions('attribute-picture', dotTemplateAttributePicture, templateVariables);
|
|
|
|
|
}
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
return renderInvalidMISPElement(scope, elementID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderInvalidMISPElement(scope, elementID) {
|
|
|
|
|
var templateVariables = sanitizeObject({
|
|
|
|
|
scope: invalidMessage,
|
|
|
|
|
id: elementID
|
|
|
|
|
})
|
|
|
|
|
return dotTemplateInvalid(templateVariables);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-06 10:04:13 +02:00
|
|
|
|
function renderTemplateBasedOnRenderingOptions(scope, templateToRender, templateVariables) {
|
|
|
|
|
if (renderingRules[scope]) {
|
|
|
|
|
return templateToRender(templateVariables)
|
|
|
|
|
} else {
|
|
|
|
|
return dotTemplateRenderingDisabled(templateVariables)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-14 08:33:59 +02:00
|
|
|
|
function setupMISPElementMarkdownListeners() {
|
2020-10-18 16:36:51 +02:00
|
|
|
|
var $elements = $('.misp-element-wrapper.attribute:not(".suggestion"), .misp-element-wrapper.object, .misp-picture-wrapper > img, .embeddedTag');
|
|
|
|
|
$elements.popover({
|
2020-10-01 16:55:17 +02:00
|
|
|
|
trigger: 'click',
|
|
|
|
|
html: true,
|
2020-10-09 13:34:46 +02:00
|
|
|
|
container: isInsideModal() ? 'body' : '#viewer-container',
|
2020-10-01 16:55:17 +02:00
|
|
|
|
placement: 'top',
|
|
|
|
|
title: getTitleFromMISPElementDOM,
|
|
|
|
|
content: getContentFromMISPElementDOM
|
|
|
|
|
})
|
2020-10-15 11:56:21 +02:00
|
|
|
|
setupSuggestionMarkdownListeners()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setupSuggestionMarkdownListeners() {
|
|
|
|
|
$('.misp-element-wrapper').filter('.suggestion').click(function(e) {
|
|
|
|
|
var $checkbox = $(this).find('input[type="checkbox"]')
|
|
|
|
|
$checkbox.prop('checked', !$checkbox.prop('checked'))
|
|
|
|
|
updateSuggestionCheckedState($(this), $checkbox)
|
|
|
|
|
}).find('input[type="checkbox"]')
|
|
|
|
|
.click(function(e) {
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
})
|
|
|
|
|
.change(function() {
|
|
|
|
|
updateSuggestionCheckedState($(this).closest('.suggestion'), $(this))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
function updateSuggestionCheckedState($wrapper, $checkbox) {
|
|
|
|
|
var elementID = $wrapper.data('elementid')
|
|
|
|
|
var suggestionKey = $wrapper.data('suggestionkey')
|
|
|
|
|
suggestions[elementID][suggestionKey].checked = $checkbox.prop('checked')
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function attachRemoteMISPElements() {
|
2020-09-29 12:16:50 +02:00
|
|
|
|
$('.embeddedGalaxyMatrix[data-scope="galaxymatrix"]').each(function() {
|
2020-09-14 08:33:59 +02:00
|
|
|
|
var $div = $(this)
|
2020-09-29 12:16:50 +02:00
|
|
|
|
clearTimeout(galaxyMatrixTimer);
|
2020-09-14 08:33:59 +02:00
|
|
|
|
$div.append($('<div/>').css('font-size', '24px').append(loadingSpanAnimation))
|
2020-09-29 12:16:50 +02:00
|
|
|
|
var eventID = $div.data('eventid')
|
|
|
|
|
var elementID = $div.data('elementid')
|
|
|
|
|
var cacheKey = eventid + '-' + elementID
|
|
|
|
|
if (cache_matrix[cacheKey] === undefined) {
|
|
|
|
|
galaxyMatrixTimer = setTimeout(function() {
|
|
|
|
|
attachGalaxyMatrix($div, eventID, elementID)
|
2020-10-18 21:10:45 +02:00
|
|
|
|
}, firstCustomPostRenderCall ? 0 : slowDebounceDelay);
|
2020-09-14 08:33:59 +02:00
|
|
|
|
} else {
|
2020-09-29 12:16:50 +02:00
|
|
|
|
$div.html(cache_matrix[cacheKey])
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
})
|
2020-12-19 17:28:59 +01:00
|
|
|
|
|
|
|
|
|
var tagNamesToLoad = [];
|
|
|
|
|
var tagsLoading = [];
|
2020-10-01 16:55:17 +02:00
|
|
|
|
$('.embeddedTag[data-scope="tag"]').each(function() {
|
2020-12-19 17:28:59 +01:00
|
|
|
|
var $div = $(this);
|
|
|
|
|
var elementID = $div.data('elementid');
|
|
|
|
|
if (!(elementID in cache_tag)) {
|
|
|
|
|
$div.append($('<span/>').append(loadingSpanAnimation));
|
|
|
|
|
tagNamesToLoad.push(elementID);
|
|
|
|
|
tagsLoading.push($div);
|
2020-10-01 16:55:17 +02:00
|
|
|
|
} else {
|
2020-12-19 17:28:59 +01:00
|
|
|
|
$div.html(cache_tag[elementID]);
|
2020-10-01 16:55:17 +02:00
|
|
|
|
}
|
2020-12-19 17:28:59 +01:00
|
|
|
|
}).promise().done(function() {
|
|
|
|
|
if (tagNamesToLoad.length === 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
fetchTagInfo(tagNamesToLoad, function() {
|
|
|
|
|
$.each(tagsLoading, function() {
|
|
|
|
|
var $div = $(this);
|
|
|
|
|
var elementID = $div.data('elementid');
|
|
|
|
|
if (elementID in cache_tag) {
|
|
|
|
|
$div.html(cache_tag[elementID]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2020-10-18 21:10:45 +02:00
|
|
|
|
if (firstCustomPostRenderCall) {
|
|
|
|
|
// Wait, because .each calls are asynchronous
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
firstCustomPostRenderCall = false;
|
|
|
|
|
}, 1000)
|
|
|
|
|
}
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-29 12:16:50 +02:00
|
|
|
|
function attachGalaxyMatrix($elem, eventid, elementID) {
|
|
|
|
|
var galaxy = proxyMISPElements['galaxymatrix'][elementID]
|
|
|
|
|
if (galaxy === undefined) {
|
|
|
|
|
console.log('Something wrong happened. Could not fetch galaxy from proxy')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var galaxyType = galaxy.type
|
2020-09-14 08:33:59 +02:00
|
|
|
|
$.ajax({
|
|
|
|
|
data: {
|
|
|
|
|
"returnFormat": "attack",
|
2020-09-29 12:16:50 +02:00
|
|
|
|
"eventid": eventid,
|
|
|
|
|
"attackGalaxy": galaxyType
|
2020-09-14 08:33:59 +02:00
|
|
|
|
},
|
|
|
|
|
success:function(data, textStatus) {
|
|
|
|
|
$elem.empty().append($(data))
|
|
|
|
|
$elem.find('#attackmatrix_div').css({
|
|
|
|
|
'max-width': 'unset',
|
|
|
|
|
'min-width': 'unset',
|
|
|
|
|
'width': 'calc(100% - 5px)'
|
|
|
|
|
})
|
|
|
|
|
$elem.find('#checkbox_attackMatrix_showAll').click()
|
|
|
|
|
$elem.find('#attackmatrix_div .heatCell').each(function() {
|
|
|
|
|
if ($(this).css('background-color').length > 0 && $(this).css('background-color') != 'rgba(0, 0, 0, 0)') {
|
|
|
|
|
$(this).attr('style', 'background-color:' + $(this).css('background-color') + ' !important; color:' + $(this).css('color') + ' !important;');
|
|
|
|
|
}
|
|
|
|
|
})
|
2020-09-29 12:16:50 +02:00
|
|
|
|
var cacheKey = eventid + '-' + elementID
|
|
|
|
|
cache_matrix[cacheKey] = $elem.find('#attackmatrix_div')[0].outerHTML;
|
2020-09-14 08:33:59 +02:00
|
|
|
|
},
|
|
|
|
|
error: function(jqXHR, textStatus, errorThrown) {
|
|
|
|
|
var templateVariables = sanitizeObject({
|
|
|
|
|
scope: 'Error while fetching matrix',
|
|
|
|
|
id: graphID
|
|
|
|
|
})
|
|
|
|
|
var placeholder = dotTemplateInvalid(templateVariables)
|
|
|
|
|
$elem.empty()
|
|
|
|
|
.css({'text-align': 'center'})
|
|
|
|
|
.append($(placeholder))
|
|
|
|
|
},
|
|
|
|
|
type:"post",
|
|
|
|
|
url: baseurl + "/events/restSearch"
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-19 17:28:59 +01:00
|
|
|
|
function fetchTagInfo(tagNames, callback) {
|
2020-10-01 16:55:17 +02:00
|
|
|
|
$.ajax({
|
|
|
|
|
data: {
|
2020-12-19 17:28:59 +01:00
|
|
|
|
"tag": tagNames,
|
2020-10-01 16:55:17 +02:00
|
|
|
|
},
|
2020-12-19 17:28:59 +01:00
|
|
|
|
success: function (data) {
|
|
|
|
|
var $tag, tagName;
|
2020-10-01 16:55:17 +02:00
|
|
|
|
data = $.parseJSON(data)
|
|
|
|
|
for (var i = 0; i < data.length; i++) {
|
|
|
|
|
var tag = data[i];
|
2020-12-19 17:28:59 +01:00
|
|
|
|
tagName = tag.Tag.name;
|
|
|
|
|
|
|
|
|
|
proxyMISPElements['tag'][tagName] = tag;
|
|
|
|
|
|
|
|
|
|
$tag = getTagReprensentation(tag);
|
|
|
|
|
cache_tag[tagName] = $tag[0].outerHTML;
|
2020-10-01 16:55:17 +02:00
|
|
|
|
}
|
2020-12-19 17:28:59 +01:00
|
|
|
|
|
|
|
|
|
// If tag name doesn't exists, construct empty placeholder
|
|
|
|
|
for (i = 0; i < tagNames.length; i++) {
|
|
|
|
|
tagName = tagNames[i];
|
|
|
|
|
if (!(tagName in cache_tag)) {
|
|
|
|
|
$tag = constructTagHtml(tagName, '#ffffff', {'border': '1px solid #000'});
|
|
|
|
|
cache_tag[tagName] = $tag[0].outerHTML;
|
|
|
|
|
}
|
2020-10-01 16:55:17 +02:00
|
|
|
|
}
|
|
|
|
|
},
|
2020-12-19 17:28:59 +01:00
|
|
|
|
error: function (jqXHR, textStatus, errorThrown) {
|
|
|
|
|
// Query failed, fill cache with placeholder
|
|
|
|
|
var tagName, templateVariables;
|
|
|
|
|
for (var i = 0; i < tagNames.length; i++) {
|
|
|
|
|
tagName = tagNames[i];
|
|
|
|
|
if (!(tagName in cache_tag)) {
|
|
|
|
|
templateVariables = sanitizeObject({
|
|
|
|
|
scope: 'Error while fetching tag',
|
|
|
|
|
id: tagName
|
|
|
|
|
});
|
|
|
|
|
cache_tag[tagName] = dotTemplateInvalid(templateVariables);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-19 18:12:41 +02:00
|
|
|
|
},
|
2020-12-19 17:28:59 +01:00
|
|
|
|
complete: function () {
|
2020-10-19 18:12:41 +02:00
|
|
|
|
if (callback !== undefined) {
|
|
|
|
|
callback()
|
|
|
|
|
}
|
2020-10-01 16:55:17 +02:00
|
|
|
|
},
|
2020-12-19 17:28:59 +01:00
|
|
|
|
type: "post",
|
2020-10-02 11:41:03 +02:00
|
|
|
|
url: baseurl + "/tags/search/0/1/0"
|
2020-10-01 16:55:17 +02:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-14 08:33:59 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
_____ _
|
|
|
|
|
/ ____| (_)
|
|
|
|
|
| (___ __ ___ ___ _ __ __ _
|
|
|
|
|
\___ \ / _` \ \ / / | '_ \ / _` |
|
|
|
|
|
____) | (_| |\ V /| | | | | (_| |
|
|
|
|
|
|_____/ \__,_| \_/ |_|_| |_|\__, |
|
|
|
|
|
__/ |
|
|
|
|
|
|___/
|
|
|
|
|
*/
|
|
|
|
|
function replaceMISPElementByTheirValue(raw) {
|
|
|
|
|
var match, replacement, element
|
|
|
|
|
var final = ''
|
|
|
|
|
var authorizedMISPElements = ['attribute', 'object']
|
|
|
|
|
var reMISPElement = RegExp('@\\[(?<scope>' + authorizedMISPElements.join('|') + ')\\]\\((?<elementid>[\\d]+)\\)', 'g');
|
|
|
|
|
var offset = 0
|
|
|
|
|
while ((match = reMISPElement.exec(raw)) !== null) {
|
|
|
|
|
element = proxyMISPElements[match.groups.scope][match.groups.elementid]
|
|
|
|
|
if (element !== undefined) {
|
|
|
|
|
replacement = match.groups.scope + '-' + element.uuid
|
|
|
|
|
} else {
|
|
|
|
|
replacement = match.groups.scope + '-' + match.groups.elementid
|
|
|
|
|
}
|
|
|
|
|
final += raw.substring(offset, match.index) + replacement
|
|
|
|
|
offset = reMISPElement.lastIndex
|
|
|
|
|
}
|
|
|
|
|
final += raw.substring(offset)
|
|
|
|
|
return final
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-17 07:55:21 +02:00
|
|
|
|
/**
|
|
|
|
|
__ __
|
|
|
|
|
| \/ |
|
|
|
|
|
| \ / | ___ _ __ _ _
|
|
|
|
|
| |\/| |/ _ \ '_ \| | | |
|
|
|
|
|
| | | | __/ | | | |_| |
|
|
|
|
|
|_| |_|\___|_| |_|\__,_|
|
|
|
|
|
*/
|
|
|
|
|
function injectCustomRulesMenu() {
|
2020-10-06 10:04:13 +02:00
|
|
|
|
var $MISPElementMenuItem = createRulesMenuItem('MISP Elements', $('<img src="/favicon.ico">'), 'parser', 'MISP_element_rule')
|
2020-09-17 07:55:21 +02:00
|
|
|
|
$markdownDropdownRulesMenu.append($MISPElementMenuItem)
|
2020-10-06 10:04:13 +02:00
|
|
|
|
createSubMenu({
|
|
|
|
|
name: 'Markdown rendering rules',
|
|
|
|
|
icon: 'fab fa-markdown',
|
|
|
|
|
items: [
|
2020-10-15 11:56:21 +02:00
|
|
|
|
{ name: 'Attribute', icon: 'fas fa-cube', ruleScope: 'render', ruleName: 'attribute', isToggleableRule: true },
|
|
|
|
|
{ name: 'Attribute picture', icon: 'fas fa-image', ruleScope: 'render', ruleName: 'attribute-picture', isToggleableRule: true },
|
|
|
|
|
{ name: 'Object', icon: 'fas fa-cubes', ruleScope: 'render', ruleName: 'object', isToggleableRule: true },
|
|
|
|
|
{ name: 'Object Attribute', icon: 'fas fa-cube', ruleScope: 'render', ruleName: 'object-attribute', isToggleableRule: true },
|
|
|
|
|
{ name: 'Tag', icon: 'fas fa-tag', ruleScope: 'render', ruleName: 'tag', isToggleableRule: true },
|
|
|
|
|
{ name: 'Galaxy matrix', icon: 'fas fa-atlas', ruleScope: 'render', ruleName: 'galaxymatrix', isToggleableRule: true },
|
|
|
|
|
{ name: 'Suggestion', icon: 'fas fa-magic', ruleScope: 'render', ruleName: 'suggestion', isToggleableRule: true },
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
createSubMenu({
|
|
|
|
|
name: 'Extract entities',
|
|
|
|
|
icon: 'fas fa-cogs',
|
|
|
|
|
items: [
|
|
|
|
|
{ name: 'Manual extraction', icon: 'fas fa-highlighter', clickHandler: manualEntitiesExtraction},
|
|
|
|
|
{ name: 'Automatic extraction', icon: 'fas fa-magic', clickHandler: automaticEntitiesExtraction},
|
2020-10-06 10:04:13 +02:00
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
reloadRenderingRuleEnabledUI()
|
2020-09-17 07:55:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function markdownItToggleCustomRule(rulename, event) {
|
|
|
|
|
var enabled
|
|
|
|
|
if (rulename == 'MISP_element_rule') {
|
|
|
|
|
var rule = getRuleStatus('inline', 'ruler', 'MISP_element_rule')
|
|
|
|
|
if (rule !== false) {
|
|
|
|
|
enabled = rule.enabled
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
found: enabled !== undefined,
|
|
|
|
|
enabled: enabled
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-14 08:33:59 +02:00
|
|
|
|
|
2020-10-15 11:56:21 +02:00
|
|
|
|
function markdownItToggleRenderingRule(rulename, event) {
|
|
|
|
|
if (event !== undefined) {
|
|
|
|
|
event.stopPropagation()
|
|
|
|
|
}
|
|
|
|
|
if (renderingRules[rulename] === undefined) {
|
2022-11-08 16:13:14 +01:00
|
|
|
|
console.log('Rule does not exist')
|
2020-10-15 11:56:21 +02:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
renderingRules[rulename] = !renderingRules[rulename]
|
|
|
|
|
doRender()
|
|
|
|
|
reloadRenderingRuleEnabledUI()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function reloadRenderingRuleEnabledUI() {
|
|
|
|
|
Object.keys(renderingRules).forEach(function(rulename) {
|
|
|
|
|
var enabled = renderingRules[rulename]
|
|
|
|
|
if (enabled) {
|
|
|
|
|
$('#markdownrendering-' + rulename + '-rendering-enabled').show()
|
|
|
|
|
$('#markdownrendering-' + rulename + '-rendering-disabled').hide()
|
|
|
|
|
} else {
|
|
|
|
|
$('#markdownrendering-' + rulename + '-rendering-enabled').hide()
|
|
|
|
|
$('#markdownrendering-' + rulename + '-rendering-disabled').show()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2020-10-18 16:36:51 +02:00
|
|
|
|
_____ _ _
|
|
|
|
|
/ ____| | | (_)
|
|
|
|
|
| (___ _ _ __ _ __ _ ___ ___| |_ _ ___ _ __
|
|
|
|
|
\___ \| | | |/ _` |/ _` |/ _ \/ __| __| |/ _ \| '_ \
|
2020-10-15 11:56:21 +02:00
|
|
|
|
____) | |_| | (_| | (_| | __/\__ \ |_| | (_) | | | |
|
|
|
|
|
|_____/ \__,_|\__, |\__, |\___||___/\__|_|\___/|_| |_|
|
2020-10-18 16:36:51 +02:00
|
|
|
|
__/ | __/ |
|
|
|
|
|
|___/ |___/
|
2020-10-15 11:56:21 +02:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
function automaticEntitiesExtraction() {
|
2020-10-19 10:27:37 +02:00
|
|
|
|
var url = baseurl + '/eventReports/extractAllFromReport/' + reportid
|
2020-10-23 23:32:56 +02:00
|
|
|
|
openGenericModal(url)
|
2020-10-15 11:56:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function manualEntitiesExtraction() {
|
|
|
|
|
contentBeforeSuggestions = getEditorData()
|
2020-10-19 18:12:41 +02:00
|
|
|
|
pickedSuggestion = { tableID: null, tr: null, entity: null, index: null, isContext: null }
|
2020-10-19 10:27:37 +02:00
|
|
|
|
extractFromReport(function(data) {
|
2020-10-15 11:56:21 +02:00
|
|
|
|
typeToCategoryMapping = data.typeToCategoryMapping
|
2020-10-19 18:12:41 +02:00
|
|
|
|
prepareSuggestionInterface(data.complexTypeToolResult, data.replacementValues, data.replacementContext)
|
2020-10-16 18:42:43 +02:00
|
|
|
|
toggleSuggestionInterface(true)
|
2020-10-15 11:56:21 +02:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 18:12:41 +02:00
|
|
|
|
function prepareSuggestionInterface(complexTypeToolResult, replacementValues, replacementContext) {
|
2020-10-15 17:01:53 +02:00
|
|
|
|
toggleMarkdownEditorLoading(true, 'Processing document')
|
2020-10-19 12:26:13 +02:00
|
|
|
|
entitiesFromComplexTool = complexTypeToolResult
|
2020-10-19 18:12:41 +02:00
|
|
|
|
searchForUnreferencedValues(replacementValues)
|
|
|
|
|
searchForUnreferencedContext(replacementContext)
|
2020-10-23 22:07:13 +02:00
|
|
|
|
entitiesFromComplexTool = injectNumberOfOccurrencesInReport(entitiesFromComplexTool)
|
2020-10-15 11:56:21 +02:00
|
|
|
|
setupSuggestionMarkdownListeners()
|
2020-10-16 08:44:23 +02:00
|
|
|
|
constructSuggestionTables(entitiesFromComplexTool)
|
2020-10-15 17:01:53 +02:00
|
|
|
|
toggleMarkdownEditorLoading(false)
|
2020-10-15 11:56:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 17:01:53 +02:00
|
|
|
|
function highlightPickedSuggestionInReport() {
|
2020-10-15 11:56:21 +02:00
|
|
|
|
setEditorData(contentBeforeSuggestions)
|
2020-10-21 11:28:44 +02:00
|
|
|
|
resetSuggestionIDs()
|
2020-10-15 11:56:21 +02:00
|
|
|
|
for (var i = 0; i < entitiesFromComplexTool.length; i++) {
|
|
|
|
|
var entity = entitiesFromComplexTool[i];
|
|
|
|
|
if (pickedSuggestion.entity.value == entity.value) {
|
2020-10-15 17:01:53 +02:00
|
|
|
|
var converted = convertEntityIntoSuggestion(contentBeforeSuggestions, entity)
|
2020-10-15 11:56:21 +02:00
|
|
|
|
setEditorData(converted)
|
|
|
|
|
var indicesInCM = getAllSuggestionIndicesOf(converted, entity.value, false)
|
|
|
|
|
constructSuggestionMapping(entity, indicesInCM)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 17:01:53 +02:00
|
|
|
|
function highlightPickedReplacementInReport() {
|
|
|
|
|
var entity = pickedSuggestion.entity
|
|
|
|
|
setEditorData(contentBeforeSuggestions)
|
|
|
|
|
var content = contentBeforeSuggestions
|
2020-10-21 11:28:44 +02:00
|
|
|
|
resetSuggestionIDs()
|
2020-10-15 17:01:53 +02:00
|
|
|
|
var converted = convertEntityIntoSuggestion(content, entity)
|
|
|
|
|
setEditorData(converted)
|
|
|
|
|
var indicesInCM = getAllSuggestionIndicesOf(converted, entity.value, false)
|
|
|
|
|
constructSuggestionMapping(entity, indicesInCM)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 11:56:21 +02:00
|
|
|
|
function convertEntityIntoSuggestion(content, entity) {
|
|
|
|
|
var converted = ''
|
2020-12-19 12:43:24 +01:00
|
|
|
|
var entityValue;
|
|
|
|
|
if (entity.importRegexMatch) {
|
|
|
|
|
entityValue = entity.importRegexMatch;
|
|
|
|
|
} else if (entity.original_value) {
|
|
|
|
|
entityValue = entity.original_value;
|
|
|
|
|
} else {
|
|
|
|
|
entityValue = entity.value;
|
|
|
|
|
}
|
2020-10-16 18:42:43 +02:00
|
|
|
|
var splittedContent = content.split(entityValue)
|
2020-10-15 11:56:21 +02:00
|
|
|
|
splittedContent.forEach(function(text, i) {
|
|
|
|
|
converted += text
|
|
|
|
|
if (i < splittedContent.length-1) {
|
2020-10-20 08:28:50 +02:00
|
|
|
|
if (isDoubleExtraction(converted)) {
|
|
|
|
|
converted += entity.value
|
|
|
|
|
} else {
|
|
|
|
|
converted += '@[suggestion](' + entity.value + ')'
|
|
|
|
|
}
|
2020-10-15 11:56:21 +02:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return converted
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 10:27:37 +02:00
|
|
|
|
function extractFromReport(callback) {
|
2020-10-15 11:56:21 +02:00
|
|
|
|
$.ajax({
|
|
|
|
|
dataType: "json",
|
2020-10-23 22:34:18 +02:00
|
|
|
|
beforeSend: function() {
|
|
|
|
|
toggleMarkdownEditorLoading(true, 'Extracting entities')
|
|
|
|
|
},
|
2020-10-15 11:56:21 +02:00
|
|
|
|
success:function(data, textStatus) {
|
|
|
|
|
callback(data);
|
|
|
|
|
},
|
|
|
|
|
error: function(jqXHR, textStatus, errorThrown) {
|
2020-10-16 18:42:43 +02:00
|
|
|
|
showMessage('fail', 'Could not extract entities from report. ' + textStatus)
|
2020-10-15 11:56:21 +02:00
|
|
|
|
},
|
2020-10-23 22:34:18 +02:00
|
|
|
|
complete: function() {
|
|
|
|
|
toggleMarkdownEditorLoading(false)
|
|
|
|
|
},
|
2020-10-15 11:56:21 +02:00
|
|
|
|
type:'get',
|
|
|
|
|
url: baseurl + '/eventReports/extractFromReport/' + reportid
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function constructSuggestionMapping(entity, indicesInCM) {
|
|
|
|
|
var suggestionBaseKey = 'suggestion-', suggestionKey
|
|
|
|
|
suggestions[entity.value] = {}
|
|
|
|
|
indicesInCM.forEach(function(index) {
|
2020-10-23 22:07:13 +02:00
|
|
|
|
suggestionKey = suggestionBaseKey + getNewSuggestionID()
|
2020-10-15 11:56:21 +02:00
|
|
|
|
suggestions[entity.value][suggestionKey] = {
|
|
|
|
|
startIndex: index,
|
|
|
|
|
endIndex: {index: index.index + entity.value.length},
|
|
|
|
|
complexTypeToolResult: entity,
|
|
|
|
|
checked: true
|
|
|
|
|
}
|
|
|
|
|
});
|
2020-10-23 22:07:13 +02:00
|
|
|
|
setTimeout(function() {
|
|
|
|
|
var notRenderedCount = suggestionIDs.length
|
|
|
|
|
if (notRenderedCount > 0) {
|
|
|
|
|
pickedSuggestion.tr.find('.occurrence-issues')
|
|
|
|
|
.attr('title', 'Could not render ' + notRenderedCount + ' elements. Manual investigation required')
|
|
|
|
|
.text('⚠ ' + notRenderedCount)
|
|
|
|
|
}
|
|
|
|
|
}, 300);
|
2020-10-15 11:56:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-23 22:07:13 +02:00
|
|
|
|
function injectNumberOfOccurrencesInReport(entities) {
|
2020-10-15 11:56:21 +02:00
|
|
|
|
var content = getEditorData()
|
|
|
|
|
entities.forEach(function(entity, i) {
|
2020-12-19 12:43:24 +01:00
|
|
|
|
entities[i].occurrences = getAllIndicesOf(content, entity.original_value, false, false).length
|
2020-10-15 11:56:21 +02:00
|
|
|
|
})
|
|
|
|
|
return entities
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getAllSuggestionIndicesOf(content, entity, caseSensitive) {
|
|
|
|
|
var toSearch = '@[suggestion](' + entity + ')'
|
|
|
|
|
return getAllIndicesOf(content, toSearch, caseSensitive, true)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-16 18:42:43 +02:00
|
|
|
|
function toggleSuggestionInterface(enabled) {
|
|
|
|
|
if (enabled) {
|
|
|
|
|
setCMReadOnly(true)
|
|
|
|
|
setMode('splitscreen')
|
|
|
|
|
$('#editor-subcontainer').hide()
|
|
|
|
|
$suggestionContainer.show()
|
|
|
|
|
} else {
|
|
|
|
|
setCMReadOnly(false)
|
|
|
|
|
setEditorData(originalRaw)
|
|
|
|
|
$('#editor-subcontainer').show()
|
|
|
|
|
$suggestionContainer.hide()
|
|
|
|
|
$mardownViewerToolbar.find('.btn-group:first button').css('visibility', 'visible')
|
|
|
|
|
$('#suggestionCloseButton').remove()
|
|
|
|
|
cm.refresh()
|
|
|
|
|
}
|
2020-10-15 11:56:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 18:12:41 +02:00
|
|
|
|
function searchForUnreferencedValues(replacementValues) {
|
|
|
|
|
unreferencedElements.values = {}
|
2020-10-16 11:38:00 +02:00
|
|
|
|
var content = getEditorData()
|
2020-10-19 12:26:13 +02:00
|
|
|
|
Object.keys(replacementValues).forEach(function(attributeValue) {
|
|
|
|
|
var replacementValue = replacementValues[attributeValue]
|
|
|
|
|
var indices = getAllIndicesOf(content, replacementValue.valueInReport, true, true)
|
2020-10-16 11:38:00 +02:00
|
|
|
|
if (indices.length > 0) {
|
2020-10-19 12:26:13 +02:00
|
|
|
|
var attributes = [];
|
|
|
|
|
Object.keys(proxyMISPElements['attribute']).forEach(function(uuid) {
|
|
|
|
|
if (replacementValue.attributeUUIDs.indexOf(uuid) > -1) {
|
|
|
|
|
attributes.push(proxyMISPElements['attribute'][uuid])
|
2020-10-16 11:38:00 +02:00
|
|
|
|
}
|
2020-10-19 12:26:13 +02:00
|
|
|
|
});
|
2020-10-19 18:12:41 +02:00
|
|
|
|
unreferencedElements.values[replacementValue.valueInReport] = {
|
2020-10-19 12:26:13 +02:00
|
|
|
|
attributes: attributes,
|
|
|
|
|
indices: indices
|
2020-10-16 11:38:00 +02:00
|
|
|
|
}
|
2020-10-19 12:26:13 +02:00
|
|
|
|
if (attributeValue != replacementValue.valueInReport) {
|
2020-10-19 18:12:41 +02:00
|
|
|
|
unreferencedElements.values[replacementValue.valueInReport].importRegexMatch = attributeValue
|
2020-10-16 18:42:43 +02:00
|
|
|
|
}
|
2020-10-16 11:38:00 +02:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 18:12:41 +02:00
|
|
|
|
function searchForUnreferencedContext(replacementContext) {
|
|
|
|
|
unreferencedElements.context = {}
|
|
|
|
|
var content = getEditorData()
|
|
|
|
|
Object.keys(replacementContext).forEach(function(rawText) {
|
|
|
|
|
var indices = getAllIndicesOf(content, rawText, true, true)
|
|
|
|
|
if (indices.length > 0) {
|
|
|
|
|
replacementContext[rawText].indices = indices
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
unreferencedElements.context = replacementContext;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-16 11:38:00 +02:00
|
|
|
|
function pickSuggestionColumn(index, tableID, force) {
|
|
|
|
|
tableID = tableID === undefined ? 'replacementTable' : tableID
|
|
|
|
|
force = force === undefined ? false : force;
|
|
|
|
|
if (pickedSuggestion.tableID != tableID || pickedSuggestion.index != index || force) {
|
2020-10-23 22:07:13 +02:00
|
|
|
|
if (pickedSuggestion.tr) {
|
|
|
|
|
pickedSuggestion.tr.find('.occurrence-issues').attr('title', '').text('')
|
|
|
|
|
}
|
2020-10-16 11:38:00 +02:00
|
|
|
|
var $trs = $('#' + tableID + ' tr')
|
|
|
|
|
$trs.removeClass('info').find('button').prop('disabled', true)
|
|
|
|
|
$trs.find('select').prop('disabled', true)
|
|
|
|
|
var $tr = $('#' + tableID + ' tr[data-entityindex="' + index + '"]')
|
|
|
|
|
if ($tr.length > 0) {
|
|
|
|
|
$tr.addClass('info').find('button').prop('disabled', false)
|
|
|
|
|
$tr.find('select').prop('disabled', false)
|
|
|
|
|
pickedSuggestion = {
|
|
|
|
|
tableID: tableID,
|
|
|
|
|
tr: $tr,
|
|
|
|
|
index: index
|
|
|
|
|
}
|
2020-12-19 12:43:24 +01:00
|
|
|
|
if (tableID === 'replacementTable') {
|
2020-10-16 11:38:00 +02:00
|
|
|
|
var uuid = $tr.find('select.attribute-replacement').val()
|
|
|
|
|
pickedSuggestion['entity'] = {
|
|
|
|
|
value: $tr.data('attributeValue'),
|
|
|
|
|
picked_type: proxyMISPElements['attribute'][uuid].type,
|
|
|
|
|
replacement: uuid
|
|
|
|
|
}
|
2020-10-16 18:42:43 +02:00
|
|
|
|
if (proxyMISPElements['attribute'][uuid].importRegexValue) {
|
|
|
|
|
pickedSuggestion['entity']['importRegexMatch'] = proxyMISPElements['attribute'][uuid].importRegexValue
|
|
|
|
|
}
|
2020-10-16 11:38:00 +02:00
|
|
|
|
highlightPickedReplacementInReport()
|
2020-12-19 12:43:24 +01:00
|
|
|
|
} else if (tableID === 'contextReplacementTable') {
|
2020-10-19 18:12:41 +02:00
|
|
|
|
pickedSuggestion['entity'] = {
|
|
|
|
|
value: $tr.data('contextValue'),
|
|
|
|
|
picked_type: 'tag',
|
|
|
|
|
replacement: $tr.find('select.context-replacement').val()
|
|
|
|
|
}
|
|
|
|
|
pickedSuggestion['isContext'] = true
|
|
|
|
|
highlightPickedReplacementInReport()
|
2020-10-16 11:38:00 +02:00
|
|
|
|
} else {
|
|
|
|
|
pickedSuggestion['entity'] = $tr.data('entity')
|
|
|
|
|
pickedSuggestion['entity']['picked_type'] = $tr.find('select.type').val()
|
|
|
|
|
highlightPickedSuggestionInReport()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 10:27:37 +02:00
|
|
|
|
function getContentWithCheckedElements(isReplacement) {
|
2020-10-16 11:38:00 +02:00
|
|
|
|
var value = pickedSuggestion.entity.value
|
|
|
|
|
var content = getEditorData()
|
|
|
|
|
var contentWithPickedSuggestions = ''
|
|
|
|
|
var nextIndex = 0
|
|
|
|
|
var suggestionLength = '@[suggestion]()'.length + pickedSuggestion.entity.value.length
|
|
|
|
|
Object.keys(suggestions[value]).forEach(function(suggestionKey, i) {
|
|
|
|
|
var suggestion = suggestions[value][suggestionKey]
|
|
|
|
|
contentWithPickedSuggestions += content.substr(nextIndex, suggestion.startIndex.index - nextIndex)
|
|
|
|
|
nextIndex = suggestion.startIndex.index
|
2020-10-23 22:07:13 +02:00
|
|
|
|
var renderedInMardown = $('.misp-element-wrapper.suggestion[data-suggestionkey="' + suggestionKey + '"]').length > 0;
|
2020-10-21 11:28:44 +02:00
|
|
|
|
if (suggestion.checked && renderedInMardown) { // If the suggestion is not rendered, ignore it (could happen if parent block is escaped)
|
2020-10-19 10:27:37 +02:00
|
|
|
|
if (isReplacement) {
|
2020-10-23 22:07:13 +02:00
|
|
|
|
if (pickedSuggestion.isContext === true) {
|
2020-10-19 18:12:41 +02:00
|
|
|
|
contentWithPickedSuggestions += '@[tag](' + suggestion.complexTypeToolResult.replacement + ')'
|
2020-10-23 22:07:13 +02:00
|
|
|
|
} else {
|
|
|
|
|
contentWithPickedSuggestions += '@[attribute](' + suggestion.complexTypeToolResult.replacement + ')'
|
2020-10-19 18:12:41 +02:00
|
|
|
|
}
|
2020-10-19 10:27:37 +02:00
|
|
|
|
} else {
|
|
|
|
|
contentWithPickedSuggestions += content.substr(nextIndex, suggestionLength)
|
|
|
|
|
}
|
2020-10-16 11:38:00 +02:00
|
|
|
|
} else {
|
|
|
|
|
contentWithPickedSuggestions += value
|
|
|
|
|
}
|
|
|
|
|
nextIndex += suggestionLength
|
|
|
|
|
})
|
|
|
|
|
contentWithPickedSuggestions += content.substr(nextIndex)
|
|
|
|
|
return contentWithPickedSuggestions
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getSuggestionMapping() {
|
|
|
|
|
var getSuggestionMapping = {}
|
|
|
|
|
var $select = pickedSuggestion.tr.find('select')
|
|
|
|
|
var entity = pickedSuggestion.entity
|
|
|
|
|
getSuggestionMapping[entity.value] = {
|
|
|
|
|
'type': $select.filter('.type').val(),
|
|
|
|
|
'category': $select.filter('.category').val(),
|
|
|
|
|
'to_ids': entity.to_ids
|
|
|
|
|
}
|
|
|
|
|
return getSuggestionMapping
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function submitReplacement() {
|
2020-10-19 10:27:37 +02:00
|
|
|
|
var contentWithPickedReplacements = getContentWithCheckedElements(true)
|
2020-10-16 11:38:00 +02:00
|
|
|
|
setEditorData(contentWithPickedReplacements);
|
|
|
|
|
saveMarkdown(false, function() {
|
|
|
|
|
setEditorData(originalRaw)
|
2020-10-19 12:26:13 +02:00
|
|
|
|
manualEntitiesExtraction()
|
2020-10-16 11:38:00 +02:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function submitExtractionSuggestion() {
|
|
|
|
|
var url = baseurl + '/eventReports/replaceSuggestionInReport/' + reportid
|
2020-10-19 10:27:37 +02:00
|
|
|
|
var contentWithPickedSuggestions = getContentWithCheckedElements(false)
|
2020-10-16 11:38:00 +02:00
|
|
|
|
var suggestionsMapping = getSuggestionMapping()
|
|
|
|
|
|
|
|
|
|
fetchFormDataAjax(url, function(formHTML) {
|
|
|
|
|
$('body').append($('<div id="temp" style="display: none"/>').html(formHTML))
|
|
|
|
|
var $tmpForm = $('#temp form')
|
|
|
|
|
var formUrl = $tmpForm.attr('action')
|
|
|
|
|
$tmpForm.find('[name="data[EventReport][suggestions]"]').val(JSON.stringify({
|
|
|
|
|
'content': contentWithPickedSuggestions,
|
|
|
|
|
'mapping': suggestionsMapping
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$.ajax({
|
|
|
|
|
data: $tmpForm.serialize(),
|
|
|
|
|
beforeSend: function() {
|
|
|
|
|
toggleMarkdownEditorLoading(true, 'Applying suggestions in report')
|
|
|
|
|
},
|
|
|
|
|
success:function(postResult, textStatus) {
|
|
|
|
|
if (postResult) {
|
|
|
|
|
showMessage('success', postResult.message);
|
|
|
|
|
if (postResult.data !== undefined) {
|
|
|
|
|
var report = postResult.data.report.EventReport
|
|
|
|
|
var complexTypeToolResult = postResult.data.complexTypeToolResult
|
2020-10-19 12:26:13 +02:00
|
|
|
|
var replacementValues = postResult.data.replacementValues
|
2020-10-23 22:07:13 +02:00
|
|
|
|
var replacementContext = postResult.data.replacementContext
|
2020-10-16 11:38:00 +02:00
|
|
|
|
lastModified = report.timestamp + '000'
|
|
|
|
|
refreshLastUpdatedField()
|
|
|
|
|
originalRaw = report.content
|
|
|
|
|
revalidateContentCache()
|
|
|
|
|
fetchProxyMISPElements(function() {
|
|
|
|
|
setEditorData(originalRaw)
|
|
|
|
|
contentBeforeSuggestions = originalRaw
|
2020-10-19 18:12:41 +02:00
|
|
|
|
pickedSuggestion = { tableID: null, tr: null, entity: null, index: null, isContext: null }
|
2020-10-16 11:38:00 +02:00
|
|
|
|
pickSuggestionColumn(-1)
|
2020-10-23 22:07:13 +02:00
|
|
|
|
prepareSuggestionInterface(complexTypeToolResult, replacementValues, replacementContext)
|
2020-10-16 11:38:00 +02:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
error: function(jqXHR, textStatus, errorThrown) {
|
2020-12-19 12:43:24 +01:00
|
|
|
|
if (jqXHR.responseJSON) {
|
|
|
|
|
showMessage('fail', jqXHR.responseJSON.errors);
|
|
|
|
|
} else {
|
|
|
|
|
showMessage('fail', saveFailedMessage + ': ' + errorThrown);
|
|
|
|
|
}
|
2020-10-16 11:38:00 +02:00
|
|
|
|
},
|
|
|
|
|
complete:function() {
|
|
|
|
|
$('#temp').remove();
|
|
|
|
|
toggleMarkdownEditorLoading(false)
|
|
|
|
|
},
|
|
|
|
|
type:"post",
|
|
|
|
|
url: formUrl
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-14 08:33:59 +02:00
|
|
|
|
/**
|
|
|
|
|
_ _ _ _ _
|
|
|
|
|
| | | | | (_) |
|
|
|
|
|
| | | | |_ _| |___
|
|
|
|
|
| | | | __| | / __|
|
|
|
|
|
| |__| | |_| | \__ \
|
|
|
|
|
\____/ \__|_|_|___/
|
|
|
|
|
*/
|
2020-10-08 17:00:32 +02:00
|
|
|
|
function fetchProxyMISPElements(callback) {
|
2020-10-07 11:13:30 +02:00
|
|
|
|
var url = baseurl + '/eventReports/getProxyMISPElements/' + reportid
|
|
|
|
|
var errorMessage = 'Could not fetch MISP Elements'
|
|
|
|
|
$.ajax({
|
|
|
|
|
dataType: "json",
|
|
|
|
|
url: url,
|
|
|
|
|
data: {},
|
|
|
|
|
beforeSend: function() {
|
|
|
|
|
toggleMarkdownEditorLoading(true, 'Loading MISP Elements')
|
|
|
|
|
},
|
|
|
|
|
success: function(data) {
|
|
|
|
|
if (data) {
|
|
|
|
|
proxyMISPElements = data
|
|
|
|
|
proxyMISPElements['tag'] = []
|
|
|
|
|
buildMISPElementHints()
|
|
|
|
|
} else {
|
|
|
|
|
showMessage('fail', errorMessage);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
error: function (data, textStatus, errorThrown) {
|
|
|
|
|
showMessage('fail', errorMessage + '. ' + textStatus + ": " + errorThrown);
|
|
|
|
|
},
|
|
|
|
|
complete: function() {
|
|
|
|
|
toggleMarkdownEditorLoading(false)
|
2020-10-08 17:00:32 +02:00
|
|
|
|
callback()
|
2020-10-07 11:13:30 +02:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-14 08:33:59 +02:00
|
|
|
|
function getElementFromDom(node) {
|
|
|
|
|
var scope = $(node).data('scope')
|
|
|
|
|
var elementID = $(node).data('elementid')
|
|
|
|
|
if (scope !== undefined && elementID !== undefined) {
|
|
|
|
|
return {
|
|
|
|
|
element: proxyMISPElements[scope][elementID],
|
|
|
|
|
scope: scope,
|
|
|
|
|
elementID: elementID
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getTitleFromMISPElementDOM() {
|
|
|
|
|
var data = getElementFromDom(this)
|
2020-10-16 10:17:48 +02:00
|
|
|
|
return buildTitleForMISPElement(data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildTitleForMISPElement(data) {
|
2020-09-14 08:33:59 +02:00
|
|
|
|
var title = invalidMessage
|
|
|
|
|
var dismissButton = ''
|
|
|
|
|
if (data !== false) {
|
2020-10-18 16:36:51 +02:00
|
|
|
|
var templateVariables = sanitizeObject(data)
|
|
|
|
|
dismissButton = dotCloseButtonTemplate(templateVariables)
|
2020-10-01 16:55:17 +02:00
|
|
|
|
title = data.scope.charAt(0).toUpperCase() + templateVariables.scope.slice(1) + ' ' + templateVariables.elementID
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
return title + dismissButton
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function closeThePopover(closeButton) {
|
|
|
|
|
var scope = $(closeButton).data('scope')
|
|
|
|
|
var elementID = $(closeButton).data('elementid')
|
2020-10-16 11:25:06 +02:00
|
|
|
|
var $MISPElement = $('#viewer [data-scope="' + scope + '"][data-elementid="' + elementID.replaceAll('\"', '\\\"') + '"]')
|
|
|
|
|
if ($MISPElement.length > 0) {
|
2020-10-09 14:43:10 +02:00
|
|
|
|
$MISPElement.popover('hide');
|
|
|
|
|
} else {
|
|
|
|
|
$(closeButton).closest('.popover').remove()
|
|
|
|
|
}
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-18 16:36:51 +02:00
|
|
|
|
function constructAttributeRow(attribute, fromObject) {
|
2020-10-05 14:07:51 +02:00
|
|
|
|
fromObject = fromObject !== undefined ? fromObject : false
|
|
|
|
|
var attributeFieldsToRender = ['id', 'category', 'type'].concat(fromObject ? ['object_relation'] : [], ['value', 'comment'])
|
2020-09-14 08:33:59 +02:00
|
|
|
|
var $tr = $('<tr/>')
|
|
|
|
|
attributeFieldsToRender.forEach(function(field) {
|
|
|
|
|
$tr.append(
|
|
|
|
|
$('<td/>').text(attribute[field])
|
|
|
|
|
.css('white-space', ['id', 'type'].indexOf(field) != -1 ? 'nowrap' : 'none')
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
var $tags = $('<div/>')
|
|
|
|
|
if (attribute.AttributeTag !== undefined) {
|
|
|
|
|
attribute.AttributeTag.forEach(function(attributeTag) {
|
|
|
|
|
var tag = attributeTag.Tag
|
|
|
|
|
var $tag = $('<div/>').append(
|
|
|
|
|
$('<span/>')
|
|
|
|
|
.addClass('tagComplete nowrap')
|
|
|
|
|
.css({'background-color': tag.colour, 'color': getTextColour(tag.colour), 'box-shadow': '1px 1px 3px #888888c4'})
|
|
|
|
|
.text(tag.name)
|
|
|
|
|
)
|
|
|
|
|
$tags.append($tag)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
$tr.append($('<td/>').append($tags))
|
|
|
|
|
var $galaxies = $('<div/>')
|
|
|
|
|
if (attribute.Galaxy !== undefined) {
|
|
|
|
|
attribute.Galaxy.forEach(function(galaxy) {
|
|
|
|
|
var $galaxy = $('<div/>').append(
|
|
|
|
|
$('<span/>')
|
|
|
|
|
.addClass('tagComplete nowrap')
|
|
|
|
|
.css({'background-color': '#0088cc', 'color': getTextColour('#0088cc'), 'box-shadow': '1px 1px 3px #888888c4'})
|
|
|
|
|
.text(galaxy.name + ' :: ' + galaxy.GalaxyCluster[0].value)
|
|
|
|
|
)
|
|
|
|
|
$galaxies.append($galaxy)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
$tr.append($('<td/>').append($galaxies))
|
|
|
|
|
return $tr
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-05 14:07:51 +02:00
|
|
|
|
function constructAttributeHeader(attribute, showAll, fromObject) {
|
2020-09-14 08:33:59 +02:00
|
|
|
|
showAll = showAll !== undefined ? showAll : false
|
2020-10-05 14:07:51 +02:00
|
|
|
|
fromObject = fromObject !== undefined ? fromObject : false
|
|
|
|
|
var attributeFieldsToRender = ['id', 'category', 'type'].concat(fromObject ? ['object_relation'] : [], ['value', 'comment'])
|
2020-09-14 08:33:59 +02:00
|
|
|
|
var $tr = $('<tr/>')
|
|
|
|
|
attributeFieldsToRender.forEach(function(field) {
|
|
|
|
|
$tr.append($('<th/>').text(field))
|
|
|
|
|
})
|
|
|
|
|
if (showAll || (attribute.AttributeTag !== undefined && attribute.AttributeTag.length > 0)) {
|
|
|
|
|
$tr.append($('<th/>').text('tags'))
|
|
|
|
|
}
|
|
|
|
|
if (showAll || (attribute.Galaxy !== undefined && attribute.Galaxy.length > 0)) {
|
|
|
|
|
$tr.append($('<th/>').text('galaxies'))
|
|
|
|
|
}
|
|
|
|
|
var $thead = $('<thead/>').append($tr)
|
|
|
|
|
return $thead
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function constructObject(object) {
|
|
|
|
|
var objectFieldsToRender = ['id', 'name', 'description', 'distribution']
|
|
|
|
|
var $object = $('<div/>').addClass('similarObjectPanel')
|
|
|
|
|
.css({border: '1px solid #3465a4', 'border-radius': '5px'})
|
|
|
|
|
var $top = $('<div/>').addClass('blueElement')
|
|
|
|
|
.css({padding: '4px 5px'})
|
|
|
|
|
objectFieldsToRender.forEach(function(field) {
|
|
|
|
|
$top.append($('<div/>').append(
|
|
|
|
|
$('<span/>').addClass('bold').text(field + ': '),
|
|
|
|
|
$('<span/>').text(object[field])
|
|
|
|
|
))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
var $attributeTable = $('<table/>').addClass('table table-striped table-condensed')
|
|
|
|
|
.css({'margin-bottom': '3px'})
|
2020-10-05 14:07:51 +02:00
|
|
|
|
var $thead = constructAttributeHeader({}, true, true)
|
2020-09-14 08:33:59 +02:00
|
|
|
|
var $tbody = $('<tbody/>')
|
|
|
|
|
object.Attribute.forEach(function(attribute) {
|
2020-10-05 14:07:51 +02:00
|
|
|
|
$tbody.append(constructAttributeRow(attribute, true))
|
2020-09-14 08:33:59 +02:00
|
|
|
|
})
|
|
|
|
|
$attributeTable.append($thead, $tbody)
|
|
|
|
|
$object.append($top, $attributeTable)
|
|
|
|
|
return $('<div/>').append($object)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getPriorityValue(mispObject, objectTemplate) {
|
|
|
|
|
for (var i = 0; i < objectTemplate.ObjectTemplateElement.length; i++) {
|
|
|
|
|
var object_relation = objectTemplate.ObjectTemplateElement[i].object_relation;
|
|
|
|
|
for (var j = 0; j < mispObject.Attribute.length; j++) {
|
|
|
|
|
var attribute = mispObject.Attribute[j];
|
|
|
|
|
if (attribute.object_relation === object_relation) {
|
|
|
|
|
return attribute.value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-04 11:01:04 +02:00
|
|
|
|
function getTopPriorityValue(object) {
|
|
|
|
|
var associatedTemplate = object.template_uuid + '.' + object.template_version
|
|
|
|
|
var objectTemplate = proxyMISPElements['objectTemplates'][associatedTemplate]
|
|
|
|
|
var topPriorityValue = object.Attribute.length
|
|
|
|
|
if (objectTemplate !== undefined) {
|
|
|
|
|
var temp = getPriorityValue(object, objectTemplate)
|
|
|
|
|
topPriorityValue = temp !== false ? temp : topPriorityValue
|
|
|
|
|
}
|
|
|
|
|
return topPriorityValue
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-01 16:55:17 +02:00
|
|
|
|
function constructTag(tagName) {
|
|
|
|
|
var tagData = proxyMISPElements['tag'][tagName]
|
2020-10-02 10:54:00 +02:00
|
|
|
|
var $info = 'No information about this tag'
|
|
|
|
|
if (tagData !== undefined) {
|
|
|
|
|
if (tagData.Taxonomy !== undefined) {
|
|
|
|
|
$info = constructTaxonomyInfo(tagData)
|
|
|
|
|
} else if(tagData.GalaxyCluster !== undefined) {
|
|
|
|
|
$info = constructGalaxyInfo(tagData)
|
|
|
|
|
}
|
2020-10-01 16:55:17 +02:00
|
|
|
|
}
|
|
|
|
|
return $('<div/>').append($info)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-02 11:07:52 +02:00
|
|
|
|
function getTagReprensentation(tagData) {
|
|
|
|
|
var $tag
|
2020-12-19 17:28:59 +01:00
|
|
|
|
if (tagData.GalaxyCluster !== undefined) {
|
2020-10-02 11:07:52 +02:00
|
|
|
|
$tag = constructClusterTagHtml(tagData)
|
|
|
|
|
} else {
|
2020-12-19 17:28:59 +01:00
|
|
|
|
var color = tagData.Tag.colour ? tagData.Tag.colour : tagData.TaxonomyPredicate.colour;
|
|
|
|
|
$tag = constructTagHtml(tagData.Tag.name, color)
|
2020-10-02 11:07:52 +02:00
|
|
|
|
}
|
|
|
|
|
return $tag
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-02 10:48:52 +02:00
|
|
|
|
function constructTagHtml(tagName, tagColour, additionalCSS) {
|
|
|
|
|
additionalCSS = additionalCSS === undefined ? {} : additionalCSS
|
|
|
|
|
var $tag = $('<span/>').text(tagName)
|
|
|
|
|
.addClass('tag')
|
|
|
|
|
.css({
|
|
|
|
|
'background-color': tagColour,
|
|
|
|
|
'color': getTextColour(tagColour),
|
|
|
|
|
'box-shadow': '3px 3px 3px #888888',
|
|
|
|
|
})
|
|
|
|
|
.css(additionalCSS)
|
|
|
|
|
return $tag
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-02 11:07:52 +02:00
|
|
|
|
function constructClusterTagHtml(tagData) {
|
2020-10-02 11:52:20 +02:00
|
|
|
|
var addBorder = false
|
2020-10-02 11:41:03 +02:00
|
|
|
|
if (tagData.Tag.colour === undefined) {
|
|
|
|
|
tagData.Tag.colour = '#ffffff'
|
2020-10-02 11:52:20 +02:00
|
|
|
|
addBorder = true
|
2020-10-02 11:41:03 +02:00
|
|
|
|
}
|
2020-10-02 11:07:52 +02:00
|
|
|
|
var $tag = $('<span/>').append(
|
|
|
|
|
$('<i/>').addClass('fa fa-' + tagData.GalaxyCluster.Galaxy.icon).css('margin-right', '5px'),
|
|
|
|
|
$('<span/>').text(tagData.GalaxyCluster.type + ' ↦ ' + tagData.GalaxyCluster.value)
|
|
|
|
|
)
|
|
|
|
|
.addClass('tag')
|
|
|
|
|
.css({
|
|
|
|
|
'background-color': tagData.Tag.colour,
|
|
|
|
|
'color': getTextColour(tagData.Tag.colour),
|
|
|
|
|
'box-shadow': '3px 3px 3px #888888',
|
2020-10-02 11:52:20 +02:00
|
|
|
|
'border': (addBorder ? '1px solid #000' : 'none')
|
2020-10-02 11:07:52 +02:00
|
|
|
|
})
|
|
|
|
|
return $tag
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-01 16:55:17 +02:00
|
|
|
|
function constructTaxonomyInfo(tagData) {
|
|
|
|
|
var cacheKey = eventid + '-' + tagData.Tag.name
|
|
|
|
|
var tagHTML = cache_tag[cacheKey]
|
|
|
|
|
var $tag = $(tagHTML)
|
|
|
|
|
var $predicate = $('<div/>').append(
|
|
|
|
|
$('<span/>').append($tag),
|
|
|
|
|
$('<h3/>').text('Predicate info'),
|
|
|
|
|
$('<p/>').append(
|
|
|
|
|
$('<strong/>').text('Expanded tag: '),
|
|
|
|
|
$('<span/>').text(tagData.TaxonomyPredicate.expanded),
|
|
|
|
|
),
|
|
|
|
|
$('<p/>').append(
|
|
|
|
|
$('<strong/>').text('Description: '),
|
|
|
|
|
$('<span/>').text(tagData.TaxonomyPredicate.description),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
var $meta = $('<div/>').append(
|
|
|
|
|
$('<h3/>').text('Taxonomy info'),
|
|
|
|
|
$('<p/>').append(
|
|
|
|
|
$('<strong/>').text(tagData.Taxonomy.namespace + ': '),
|
|
|
|
|
$('<span/>').text(tagData.Taxonomy.description),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
return $('<div/>').append($predicate, $meta)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function constructGalaxyInfo(tagData) {
|
2020-12-19 17:28:59 +01:00
|
|
|
|
var tagHTML = cache_tag[tagData.Tag.name]
|
2020-10-01 16:55:17 +02:00
|
|
|
|
var $tag = $(tagHTML)
|
|
|
|
|
var $cluster = $('<div/>').append(
|
|
|
|
|
$('<span/>').append($tag),
|
|
|
|
|
$('<h3/>').text('Cluster info'),
|
|
|
|
|
)
|
|
|
|
|
var fields = ['description', 'source', 'author']
|
|
|
|
|
fields.forEach(function(field) {
|
|
|
|
|
$cluster.append(
|
|
|
|
|
$('<div/>').css({
|
|
|
|
|
'max-height': '100px',
|
|
|
|
|
'overflow-y': 'auto',
|
|
|
|
|
})
|
|
|
|
|
.append(
|
|
|
|
|
$('<strong/>').text(field + ': '),
|
|
|
|
|
$('<span/>').text(tagData.GalaxyCluster[field] === undefined || tagData.GalaxyCluster[field].length == 0 ? '-' : tagData.GalaxyCluster[field]),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
var $clusterMeta = $('<div/>').css({
|
|
|
|
|
'height': '100px',
|
|
|
|
|
'overflow-y': 'auto',
|
|
|
|
|
'resize': 'vertical',
|
|
|
|
|
'border': '1px solid #0088cc',
|
|
|
|
|
'border-radius': '3px',
|
|
|
|
|
'padding': '5px'
|
|
|
|
|
})
|
|
|
|
|
if (tagData.GalaxyCluster.meta !== undefined) {
|
|
|
|
|
Object.keys(tagData.GalaxyCluster.meta).forEach(function(metaKey) {
|
|
|
|
|
var metaValue = tagData.GalaxyCluster.meta[metaKey]
|
2020-10-18 21:34:56 +02:00
|
|
|
|
if (Array.isArray(metaValue)) {
|
|
|
|
|
metaValue = metaValue.join(', ')
|
|
|
|
|
}
|
2020-10-01 16:55:17 +02:00
|
|
|
|
$clusterMeta.append(
|
|
|
|
|
$('<div/>').append(
|
|
|
|
|
$('<strong/>').addClass('blue').text(metaKey + ': '),
|
|
|
|
|
$('<span/>').text(metaValue),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
$cluster.append($clusterMeta)
|
|
|
|
|
var $galaxy = $('<div/>').append(
|
|
|
|
|
$('<h3/>').text('Galaxy info'),
|
|
|
|
|
$('<div/>').append(
|
|
|
|
|
$('<div/>').append(
|
|
|
|
|
$('<strong/>').text('Name: '),
|
|
|
|
|
$('<span/>').text(tagData.GalaxyCluster.Galaxy.name),
|
|
|
|
|
),
|
|
|
|
|
$('<div/>').append(
|
|
|
|
|
$('<strong/>').text('Description: '),
|
|
|
|
|
$('<span/>').text(tagData.GalaxyCluster.Galaxy.description),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
return $('<div/>').append($cluster, $galaxy)
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-14 08:33:59 +02:00
|
|
|
|
function getContentFromMISPElementDOM() {
|
|
|
|
|
var data = getElementFromDom(this)
|
2020-10-16 10:17:48 +02:00
|
|
|
|
return buildBodyForMISPElement(data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildBodyForMISPElement(data) {
|
2020-09-14 08:33:59 +02:00
|
|
|
|
if (data !== false) {
|
2020-10-05 14:07:51 +02:00
|
|
|
|
if (data.scope == 'attribute' && isValidObjectAttribute(data.element)) {
|
|
|
|
|
data.scope = 'object'
|
|
|
|
|
data.element = getObjectFromAttribute(data.element)
|
|
|
|
|
}
|
2020-09-14 08:33:59 +02:00
|
|
|
|
if (data.scope == 'attribute') {
|
|
|
|
|
var $thead = constructAttributeHeader(data.element)
|
|
|
|
|
var $row = constructAttributeRow(data.element)
|
|
|
|
|
var $attribute = $('<div/>').append(
|
|
|
|
|
$('<table/>')
|
|
|
|
|
.addClass('table table-condensed')
|
|
|
|
|
.append($thead)
|
|
|
|
|
.append($('<tbody/>').append($row))
|
|
|
|
|
)
|
|
|
|
|
return $attribute.html()
|
|
|
|
|
} else if (data.scope == 'object') {
|
|
|
|
|
var $object = constructObject(data.element)
|
|
|
|
|
return $object.html()
|
2020-10-01 16:55:17 +02:00
|
|
|
|
} else if (data.scope == 'tag') {
|
|
|
|
|
var $tag = constructTag(data.elementID)
|
|
|
|
|
return $tag.html()
|
2020-09-14 08:33:59 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return invalidMessage
|
2020-10-01 16:55:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-16 08:44:23 +02:00
|
|
|
|
function constructSuggestionTables(entities) {
|
2020-10-15 17:01:53 +02:00
|
|
|
|
var $extractionTable = constructExtractionTable(entities)
|
2020-10-19 18:12:41 +02:00
|
|
|
|
var $replacementTable = constructReplacementTable(unreferencedElements.values)
|
|
|
|
|
var $contextReplacementTable = constructContextReplacementTable(unreferencedElements.context)
|
2020-10-15 17:01:53 +02:00
|
|
|
|
var $collapsibleControl = $('<ul class="nav nav-tabs" id="suggestionTableTabs" />').append(
|
|
|
|
|
$('<li/>').append(
|
|
|
|
|
$('<a/>').attr('href', '#replacement-table').append(
|
2020-10-19 18:12:41 +02:00
|
|
|
|
$('<i/>').addClass('fas fa-cube'),
|
|
|
|
|
$('<span/>').text(' Data Replacement'),
|
|
|
|
|
$('<span class="badge badge-important"/>').css({'padding': '2px 6px', 'margin-left': '3px'}).text(Object.keys(unreferencedElements.values).length)
|
|
|
|
|
).attr('title', 'Replace raw text into attribute reference').css('padding', '8px 8px')
|
|
|
|
|
),
|
|
|
|
|
$('<li/>').append(
|
|
|
|
|
$('<a/>').attr('href', '#replacement-context-table').append(
|
|
|
|
|
$('<i/>').addClass('fas fa-atlas'),
|
|
|
|
|
$('<span/>').text(' Context replacement'),
|
|
|
|
|
$('<span class="badge badge-important"/>').css({'padding': '2px 6px', 'margin-left': '3px'}).text(Object.keys(unreferencedElements.context).length)
|
|
|
|
|
).attr('title', 'Replace raw text into context reference').css('padding', '8px 8px')
|
2020-10-15 17:01:53 +02:00
|
|
|
|
),
|
|
|
|
|
$('<li/>').append(
|
|
|
|
|
$('<a/>').attr('href', '#extraction-table').append(
|
2020-10-20 17:14:44 +02:00
|
|
|
|
$('<i/>').addClass('fas fa-cogs'),
|
|
|
|
|
$('<span/>').text(' Data extraction'),
|
2020-10-15 17:01:53 +02:00
|
|
|
|
$('<span class="badge badge-warning"/>').css({'padding': '2px 6px', 'margin-left': '3px'}).text(entities.length)
|
|
|
|
|
).attr('title', 'Convert raw text into attribute and reference it')
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
var $collapsibleContent = $('<div class="tab-content"/>').append(
|
|
|
|
|
$('<div class="tab-pane" id="replacement-table" />').append($replacementTable),
|
2020-10-19 18:12:41 +02:00
|
|
|
|
$('<div class="tab-pane" id="replacement-context-table" />').append($contextReplacementTable),
|
|
|
|
|
$('<div class="tab-pane" id="extraction-table" />').append($extractionTable),
|
2020-10-15 17:01:53 +02:00
|
|
|
|
$('<div class="tab-pane active" />').text('Pick a table to view available actions').css({
|
|
|
|
|
'text-align': 'center',
|
|
|
|
|
'opacity': '80%'
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
var $topBar = $('<div/>').append(
|
|
|
|
|
$('<button/>').addClass('btn btn-mini btn-inverse').css({
|
|
|
|
|
'float': 'right',
|
2020-10-16 11:25:06 +02:00
|
|
|
|
'margin-top': '8px',
|
|
|
|
|
'margin-right': '3px'
|
2020-10-15 17:01:53 +02:00
|
|
|
|
}).append(
|
|
|
|
|
$('<i/>').addClass('fas fa-expand-arrows-alt').css('margin-right', '5px'),
|
2020-10-19 18:12:41 +02:00
|
|
|
|
$('<span/>').text('Fullscreen')
|
2020-10-15 17:01:53 +02:00
|
|
|
|
).click(toggleFullscreenMode),
|
|
|
|
|
$collapsibleControl
|
|
|
|
|
)
|
2020-10-16 09:09:14 +02:00
|
|
|
|
var $div = $('<div/>').append($topBar, $collapsibleContent)
|
2020-10-15 17:01:53 +02:00
|
|
|
|
$suggestionContainer.empty().append($div)
|
|
|
|
|
addCloseSuggestionButtonToToolbar()
|
|
|
|
|
$('#suggestionTableTabs a').click(function (e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
$(this).tab('show');
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function constructExtractionTable(entities) {
|
2020-10-15 11:56:21 +02:00
|
|
|
|
var $table = $('<table/>').attr('id', 'suggestionTable').addClass('table table-striped table-condensed').css('flex-grow', '1')
|
|
|
|
|
var $thead = $('<thead/>').append($('<tr/>').append(
|
2020-10-16 10:17:48 +02:00
|
|
|
|
$('<th/>').text('Value').css('min-width', '10rem'),
|
2020-10-15 11:56:21 +02:00
|
|
|
|
$('<th/>').text('Types'),
|
|
|
|
|
$('<th/>').text('Category'),
|
|
|
|
|
$('<th/>').text('Occurrences'),
|
2020-10-15 17:01:53 +02:00
|
|
|
|
$('<th/>').text('Action')
|
2020-10-15 11:56:21 +02:00
|
|
|
|
))
|
|
|
|
|
var $tbody = $('<tbody/>')
|
|
|
|
|
entities.forEach(function(entity, index) {
|
|
|
|
|
var $selectType, $selectCategory, $option
|
|
|
|
|
if (entity.types.length > 1) {
|
2020-10-20 17:14:44 +02:00
|
|
|
|
$selectType = $('<select/>').addClass('type').css('width', 'auto').prop('disabled', true).change(function() {
|
2020-10-15 11:56:21 +02:00
|
|
|
|
var $selectCategory = $(this).closest('tr').find('select.category')
|
|
|
|
|
var selected = $(this).val()
|
|
|
|
|
var currentOptions = typeToCategoryMapping[selected];
|
|
|
|
|
$selectCategory.empty()
|
|
|
|
|
currentOptions.forEach(function(category) {
|
|
|
|
|
$selectCategory.append($('<option/>').text(category).val(category));
|
|
|
|
|
})
|
2020-10-15 17:01:53 +02:00
|
|
|
|
pickSuggestionColumn(index, 'suggestionTable', true)
|
2020-10-15 11:56:21 +02:00
|
|
|
|
})
|
|
|
|
|
entity.types.forEach(function(type) {
|
|
|
|
|
$option = $('<option/>').text(type).val(type).prop('selected', type == entity.default_type)
|
|
|
|
|
$selectType.append($option)
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
$selectType = $('<span/>').text(entity.default_type)
|
|
|
|
|
.append($('<select/>').addClass('type hidden').append($('<option/>').text(entity.default_type).val(entity.default_type)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$selectCategory = $('<select/>').addClass('category').css('width', 'auto')
|
|
|
|
|
typeToCategoryMapping[entity.default_type].forEach(function(category) {
|
|
|
|
|
$option = $('<option/>').text(category).val(category)
|
|
|
|
|
$selectCategory.append($option)
|
|
|
|
|
})
|
|
|
|
|
var $tr = $('<tr/>').attr('data-entityindex', index)
|
|
|
|
|
.data('entity', entity)
|
|
|
|
|
.addClass('useCursorPointer')
|
|
|
|
|
.append(
|
|
|
|
|
$('<td/>').addClass('bold blue').text(entity.value).css('word-wrap', 'anywhere'),
|
|
|
|
|
$('<td/>').append($selectType),
|
|
|
|
|
$('<td/>').append($selectCategory),
|
2020-10-23 22:07:13 +02:00
|
|
|
|
$('<td/>').append($('<span/>').css('white-space', 'nowrap').append(
|
|
|
|
|
$('<span/>').addClass('input-prepend input-append').append(
|
|
|
|
|
$('<button type="button"/>').attr('title', 'Jump to previous occurrence').addClass('add-on btn btn-mini').css('height', 'auto').append(
|
|
|
|
|
$('<a/>').addClass('fas fa-caret-left')
|
|
|
|
|
).click(function(e) {
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
jumpToPreviousOccurrence()
|
|
|
|
|
}),
|
|
|
|
|
$('<input type="text" disabled />').css('max-width', '2em').val(entity.occurrences),
|
|
|
|
|
$('<button type="button"/>').attr('title', 'Jump to next occurrence').addClass('add-on btn btn-mini').css('height', 'auto').append(
|
|
|
|
|
$('<a/>').addClass('fas fa-caret-right')
|
|
|
|
|
).click(function(e) {
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
jumpToNextOccurrence()
|
|
|
|
|
})
|
|
|
|
|
),
|
|
|
|
|
$('<span/>').addClass('occurrence-issues bold red').css({'margin-left': '3px'})
|
2020-10-15 11:56:21 +02:00
|
|
|
|
)),
|
|
|
|
|
$('<td/>').append(
|
|
|
|
|
$('<span/>').css('white-space', 'nowrap').append(
|
2020-10-15 17:01:53 +02:00
|
|
|
|
$('<button type="button"/>').addClass('btn')
|
|
|
|
|
.prop('disabled', true)
|
|
|
|
|
.text('Extract & Save')
|
|
|
|
|
.click(submitExtractionSuggestion)
|
2020-10-15 11:56:21 +02:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
$tr.click(function() {
|
|
|
|
|
var index = $(this).data('entityindex')
|
2020-10-15 17:01:53 +02:00
|
|
|
|
pickSuggestionColumn(index, 'suggestionTable')
|
2020-10-15 11:56:21 +02:00
|
|
|
|
})
|
|
|
|
|
$tbody.append($tr)
|
|
|
|
|
})
|
|
|
|
|
$table.append($thead, $tbody)
|
2020-10-15 17:01:53 +02:00
|
|
|
|
return $table
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 18:12:41 +02:00
|
|
|
|
function constructReplacementTable(unreferencedValues) {
|
2020-10-15 17:01:53 +02:00
|
|
|
|
var $table = $('<table/>').attr('id', 'replacementTable').addClass('table table-striped table-condensed').css('flex-grow', '1')
|
|
|
|
|
var $thead = $('<thead/>').append($('<tr/>').append(
|
2020-10-16 10:17:48 +02:00
|
|
|
|
$('<th/>').text('Value').css('min-width', '10rem'),
|
2020-10-15 17:01:53 +02:00
|
|
|
|
$('<th/>').text('Existing attribute'),
|
|
|
|
|
$('<th/>').text('Occurrences'),
|
|
|
|
|
$('<th/>').text('Action')
|
|
|
|
|
))
|
|
|
|
|
var $tbody = $('<tbody/>')
|
2020-10-19 18:12:41 +02:00
|
|
|
|
Object.keys(unreferencedValues).forEach(function(value, index) {
|
2020-10-16 11:25:06 +02:00
|
|
|
|
var $selectContainer, $select, $option
|
2020-10-19 18:12:41 +02:00
|
|
|
|
var unreferenceValue = unreferencedValues[value]
|
2020-10-15 17:01:53 +02:00
|
|
|
|
if(unreferenceValue.attributes.length > 1) {
|
2020-10-20 17:14:44 +02:00
|
|
|
|
$select = $('<select/>').prop('disabled', true).addClass('attribute-replacement').css({
|
2020-10-15 17:01:53 +02:00
|
|
|
|
'width': 'auto',
|
|
|
|
|
'max-width': '300px'
|
|
|
|
|
}).change(function() {
|
2020-10-16 11:25:06 +02:00
|
|
|
|
if ($('#viewer-container .popover.in').length > 0) {
|
|
|
|
|
$(this).parent().find('.helpicon').popover('show')
|
|
|
|
|
}
|
2020-10-15 17:01:53 +02:00
|
|
|
|
pickSuggestionColumn(index, 'replacementTable', true)
|
|
|
|
|
})
|
|
|
|
|
unreferenceValue.attributes.forEach(function(attribute) {
|
|
|
|
|
var attributeToRender = jQuery.extend(true, { }, attribute)
|
|
|
|
|
attributeToRender.value = attribute.id
|
|
|
|
|
$option = $('<option/>').val(attribute.uuid).append(renderHintElement('attribute', attributeToRender))
|
|
|
|
|
$select.append($option)
|
|
|
|
|
})
|
2020-10-16 11:25:06 +02:00
|
|
|
|
var $helpIcon = $('<a/>').css({
|
|
|
|
|
'cursor': 'help',
|
|
|
|
|
'margin-left': '3px',
|
|
|
|
|
'margin-top': '10px',
|
|
|
|
|
'vertical-align': 'top'
|
|
|
|
|
}).addClass('helpicon fas fa-question-circle')
|
|
|
|
|
.popover({
|
|
|
|
|
trigger: 'click',
|
|
|
|
|
html: true,
|
|
|
|
|
container: '#viewer-container',
|
|
|
|
|
placement: 'right',
|
|
|
|
|
title: function() {
|
|
|
|
|
var uuid = $(this).parent().find('select').val()
|
|
|
|
|
var attribute = proxyMISPElements['attribute'][uuid]
|
|
|
|
|
var popoverData = {
|
|
|
|
|
element: attribute,
|
|
|
|
|
scope: 'attribute',
|
|
|
|
|
elementID: attribute.value
|
|
|
|
|
}
|
|
|
|
|
return buildTitleForMISPElement(popoverData)
|
|
|
|
|
},
|
|
|
|
|
content: function() {
|
|
|
|
|
var uuid = $(this).parent().find('select').val()
|
|
|
|
|
var attribute = proxyMISPElements['attribute'][uuid]
|
|
|
|
|
var popoverData = {
|
|
|
|
|
element: attribute,
|
|
|
|
|
scope: 'attribute',
|
|
|
|
|
elementID: attribute.value
|
|
|
|
|
}
|
|
|
|
|
return buildBodyForMISPElement(popoverData)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
$selectContainer = $('<span/>').css({
|
|
|
|
|
'white-space': 'nowrap'
|
|
|
|
|
}).append($select, $helpIcon)
|
2020-10-15 17:01:53 +02:00
|
|
|
|
} else {
|
|
|
|
|
var attributeToRender = jQuery.extend(true, { }, unreferenceValue.attributes[0])
|
|
|
|
|
attributeToRender.value = unreferenceValue.attributes[0].id
|
2020-10-16 11:25:06 +02:00
|
|
|
|
var popoverData = {
|
|
|
|
|
element: unreferenceValue.attributes[0],
|
|
|
|
|
scope: 'attribute',
|
|
|
|
|
elementID: unreferenceValue.attributes[0].value
|
|
|
|
|
}
|
|
|
|
|
$selectContainer = $('<a/>').css({'color': 'unset', 'cursor': 'help'})
|
|
|
|
|
.popover({
|
|
|
|
|
trigger: 'click',
|
|
|
|
|
html: true,
|
|
|
|
|
container: '#viewer-container',
|
|
|
|
|
placement: 'right',
|
|
|
|
|
title: buildTitleForMISPElement(popoverData),
|
|
|
|
|
content: buildBodyForMISPElement(popoverData)
|
|
|
|
|
})
|
|
|
|
|
.append(renderHintElement('attribute', attributeToRender))
|
2020-10-15 17:01:53 +02:00
|
|
|
|
.append($('<select/>').addClass('attribute-replacement hidden').append($('<option/>').text(unreferenceValue.attributes[0].uuid).val(unreferenceValue.attributes[0].uuid)))
|
|
|
|
|
}
|
|
|
|
|
var $tr = $('<tr/>').attr('data-entityindex', index)
|
|
|
|
|
.data('attributeValue', value)
|
|
|
|
|
.addClass('useCursorPointer')
|
|
|
|
|
.append(
|
|
|
|
|
$('<td/>').addClass('bold blue').text(value).css('word-wrap', 'anywhere'),
|
2020-10-16 11:25:06 +02:00
|
|
|
|
$('<td/>').append($selectContainer),
|
2020-10-15 17:01:53 +02:00
|
|
|
|
$('<td/>').append($('<span/>').addClass('input-prepend input-append').append(
|
|
|
|
|
$('<button type="button"/>').attr('title', 'Jump to previous occurrence').addClass('add-on btn btn-mini').css('height', 'auto').append(
|
|
|
|
|
$('<a/>').addClass('fas fa-caret-left')
|
|
|
|
|
).click(function(e) {
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
jumpToPreviousOccurrence()
|
|
|
|
|
}),
|
|
|
|
|
$('<input type="text" disabled />').css('max-width', '2em').val(unreferenceValue.indices.length),
|
|
|
|
|
$('<button type="button"/>').attr('title', 'Jump to next occurrence').addClass('add-on btn btn-mini').css('height', 'auto').append(
|
|
|
|
|
$('<a/>').addClass('fas fa-caret-right')
|
|
|
|
|
).click(function(e) {
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
jumpToNextOccurrence()
|
|
|
|
|
}),
|
|
|
|
|
)),
|
|
|
|
|
$('<td/>').append(
|
|
|
|
|
$('<span/>').css('white-space', 'nowrap').append(
|
|
|
|
|
$('<button type="button"/>').addClass('btn')
|
|
|
|
|
.prop('disabled', true)
|
|
|
|
|
.text('Replace & Save')
|
|
|
|
|
.click(submitReplacement)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
$tr.click(function() {
|
|
|
|
|
var index = $(this).data('entityindex')
|
|
|
|
|
pickSuggestionColumn(index, 'replacementTable')
|
|
|
|
|
})
|
|
|
|
|
$tbody.append($tr)
|
|
|
|
|
})
|
|
|
|
|
$table.append($thead, $tbody)
|
|
|
|
|
return $table
|
2020-10-15 11:56:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 18:12:41 +02:00
|
|
|
|
function constructContextReplacementTable(unreferencedContext) {
|
|
|
|
|
var $table = $('<table/>').attr('id', 'contextReplacementTable').addClass('table table-striped table-condensed').css('flex-grow', '1')
|
|
|
|
|
var $thead = $('<thead/>').append($('<tr/>').append(
|
|
|
|
|
$('<th/>').text('Value').css('min-width', '10rem'),
|
|
|
|
|
$('<th/>').text('Existing context'),
|
|
|
|
|
$('<th/>').text('Occurrences'),
|
|
|
|
|
$('<th/>').text('Action')
|
|
|
|
|
))
|
|
|
|
|
var $tbody = $('<tbody/>')
|
|
|
|
|
Object.keys(unreferencedContext).forEach(function(rawText, index) {
|
|
|
|
|
var contexts = unreferencedContext[rawText]
|
|
|
|
|
var $selectContainer, $select, $option
|
|
|
|
|
if(Object.keys(contexts).length > 2) {
|
2020-10-20 17:14:44 +02:00
|
|
|
|
$select = $('<select/>').prop('disabled', true).addClass('context-replacement').css({
|
2020-10-19 18:12:41 +02:00
|
|
|
|
'width': 'auto',
|
|
|
|
|
'max-width': '300px'
|
|
|
|
|
}).change(function() {
|
|
|
|
|
if ($('#viewer-container .popover.in').length > 0) {
|
|
|
|
|
$(this).parent().find('.helpicon').popover('show')
|
|
|
|
|
}
|
|
|
|
|
pickSuggestionColumn(index, 'contextReplacementTable', true)
|
|
|
|
|
})
|
|
|
|
|
Object.keys(contexts).forEach(function(tagName, index) {
|
|
|
|
|
if (tagName == 'indices') {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var context = contexts[tagName]
|
|
|
|
|
var contextToRender = jQuery.extend(true, { }, context)
|
|
|
|
|
contextToRender.value = tagName
|
|
|
|
|
contextToRender.name = tagName
|
|
|
|
|
$option = $('<option/>').val(tagName).text(tagName)
|
|
|
|
|
$select.append($option)
|
|
|
|
|
})
|
|
|
|
|
$selectContainer = $('<span/>').css({
|
|
|
|
|
'white-space': 'nowrap'
|
|
|
|
|
}).append($select)
|
|
|
|
|
} else {
|
|
|
|
|
var context = jQuery.extend(true, { }, contexts)
|
|
|
|
|
delete context.indices
|
|
|
|
|
var tagName = Object.keys(context)[0]
|
|
|
|
|
context = context[tagName]
|
|
|
|
|
var contextToRender = jQuery.extend(true, { }, context)
|
|
|
|
|
contextToRender.value = tagName
|
|
|
|
|
contextToRender.name = tagName
|
|
|
|
|
$selectContainer = $('<span/>')
|
2020-10-20 17:14:44 +02:00
|
|
|
|
.append($('<span/>').append(constructTagHtml(tagName, contextToRender.colour)))
|
2020-10-19 18:12:41 +02:00
|
|
|
|
.append($('<select/>').addClass('context-replacement hidden').append($('<option/>').text(tagName).val(tagName)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var $tr = $('<tr/>').attr('data-entityindex', index)
|
|
|
|
|
.data('contextValue', rawText)
|
|
|
|
|
.addClass('useCursorPointer')
|
|
|
|
|
.append(
|
|
|
|
|
$('<td/>').addClass('bold blue').text(rawText).css('word-wrap', 'anywhere'),
|
|
|
|
|
$('<td/>').append($selectContainer),
|
|
|
|
|
$('<td/>').append($('<span/>').addClass('input-prepend input-append').append(
|
|
|
|
|
$('<button type="button"/>').attr('title', 'Jump to previous occurrence').addClass('add-on btn btn-mini').css('height', 'auto').append(
|
|
|
|
|
$('<a/>').addClass('fas fa-caret-left')
|
|
|
|
|
).click(function(e) {
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
jumpToPreviousOccurrence()
|
|
|
|
|
}),
|
|
|
|
|
$('<input type="text" disabled />').css('max-width', '2em').val(contexts.indices.length),
|
|
|
|
|
$('<button type="button"/>').attr('title', 'Jump to next occurrence').addClass('add-on btn btn-mini').css('height', 'auto').append(
|
|
|
|
|
$('<a/>').addClass('fas fa-caret-right')
|
|
|
|
|
).click(function(e) {
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
jumpToNextOccurrence()
|
|
|
|
|
}),
|
|
|
|
|
)),
|
|
|
|
|
$('<td/>').append(
|
|
|
|
|
$('<span/>').css('white-space', 'nowrap').append(
|
|
|
|
|
$('<button type="button"/>').addClass('btn')
|
|
|
|
|
.prop('disabled', true)
|
|
|
|
|
.text('Replace & Save')
|
|
|
|
|
.click(submitReplacement)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
$tr.click(function() {
|
|
|
|
|
var index = $(this).data('entityindex')
|
|
|
|
|
pickSuggestionColumn(index, 'contextReplacementTable')
|
|
|
|
|
})
|
|
|
|
|
$tbody.append($tr)
|
|
|
|
|
})
|
|
|
|
|
$table.append($thead, $tbody)
|
|
|
|
|
return $table
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 11:56:21 +02:00
|
|
|
|
function addCloseSuggestionButtonToToolbar() {
|
|
|
|
|
var $toolbarMode = $mardownViewerToolbar.find('.btn-group:first')
|
|
|
|
|
if ($toolbarMode.find('#suggestionCloseButton').length == 0) {
|
|
|
|
|
$toolbarMode.find('button').css('visibility', 'hidden')
|
|
|
|
|
var $closeButton = $('<button id="suggestionCloseButton" type="button"/>').addClass('btn btn-danger').css({
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
left: 0,
|
|
|
|
|
right: 0,
|
|
|
|
|
top: 0,
|
|
|
|
|
bottom: 0,
|
|
|
|
|
width: '100%',
|
|
|
|
|
height: '100%',
|
|
|
|
|
'border-top-left-radius': '4px',
|
|
|
|
|
'border-bottom-left-radius': '4px',
|
2020-10-16 18:42:43 +02:00
|
|
|
|
}).attr('title', 'Close manual extraction view').text('Close extraction view').click(function() { toggleSuggestionInterface(false) })
|
2020-10-15 11:56:21 +02:00
|
|
|
|
$toolbarMode.append($closeButton)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function jumpToPreviousOccurrence() {
|
|
|
|
|
var $suggestionsInReport = $('span.misp-element-wrapper.suggestion')
|
2020-10-15 17:01:53 +02:00
|
|
|
|
if ($suggestionsInReport.length > 0) {
|
|
|
|
|
var suggestionToScrollInto = $suggestionsInReport[0]
|
|
|
|
|
var $temp = $suggestionsInReport.filter('.picked')
|
|
|
|
|
if ($temp.length > 0) {
|
|
|
|
|
var index = $suggestionsInReport.index($temp)
|
|
|
|
|
if (index > 0) {
|
|
|
|
|
suggestionToScrollInto = $suggestionsInReport[index-1]
|
|
|
|
|
} else{
|
|
|
|
|
suggestionToScrollInto = $suggestionsInReport[index]
|
|
|
|
|
}
|
2020-10-15 11:56:21 +02:00
|
|
|
|
}
|
2020-10-15 17:01:53 +02:00
|
|
|
|
suggestionToScrollInto.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
|
|
|
|
|
pickOccurrence($(suggestionToScrollInto))
|
2020-10-15 11:56:21 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function jumpToNextOccurrence() {
|
|
|
|
|
var $suggestionsInReport = $('span.misp-element-wrapper.suggestion')
|
2020-10-15 17:01:53 +02:00
|
|
|
|
if ($suggestionsInReport.length > 0) {
|
|
|
|
|
var suggestionToScrollInto = $suggestionsInReport[0]
|
|
|
|
|
var $temp = $suggestionsInReport.filter('.picked')
|
|
|
|
|
if ($temp.length > 0) {
|
|
|
|
|
var index = $suggestionsInReport.index($temp)
|
|
|
|
|
if ($suggestionsInReport.length-1 > index) {
|
|
|
|
|
suggestionToScrollInto = $suggestionsInReport[index+1]
|
|
|
|
|
} else{
|
|
|
|
|
suggestionToScrollInto = $suggestionsInReport[index]
|
|
|
|
|
}
|
2020-10-15 11:56:21 +02:00
|
|
|
|
}
|
2020-10-15 17:01:53 +02:00
|
|
|
|
suggestionToScrollInto.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
|
|
|
|
|
pickOccurrence($(suggestionToScrollInto))
|
2020-10-21 11:28:44 +02:00
|
|
|
|
} else {
|
|
|
|
|
var toSearch = '@[suggestion](' + pickedSuggestion.entity.value + ')'
|
|
|
|
|
var match = $('#viewer').find('*').filter(function() {
|
|
|
|
|
return $(this).text().includes(toSearch)
|
|
|
|
|
})
|
|
|
|
|
if (match.length > 0) {
|
|
|
|
|
showMessage('success', 'Suggestion element not rendered. Please check manually')
|
|
|
|
|
match[0].scrollIntoView({ behavior: 'smooth', block: 'nearest' })
|
|
|
|
|
} else {
|
|
|
|
|
showMessage('fail', 'Could not find element')
|
|
|
|
|
}
|
2020-10-15 11:56:21 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pickOccurrence($wrapper) {
|
|
|
|
|
$('span.misp-element-wrapper.suggestion').removeClass('picked').find('.attr-type')
|
|
|
|
|
$wrapper.addClass('picked')
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-05 14:07:51 +02:00
|
|
|
|
function isValidObjectAttribute(attribute) {
|
|
|
|
|
var mispObject = getObjectFromAttribute(attribute)
|
|
|
|
|
return attribute.object_relation !== null && mispObject !== undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getObjectFromAttribute(attribute) {
|
|
|
|
|
return proxyMISPElements['object'][attribute.object_uuid]
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-20 08:28:50 +02:00
|
|
|
|
function isDoubleExtraction(content) {
|
|
|
|
|
var wrapperAttribute = '@[attribute]('
|
|
|
|
|
var wrapperTag = '@[tag]('
|
|
|
|
|
var a = content.slice(-wrapperAttribute.length)
|
|
|
|
|
a = content.slice(-wrapperTag.length)
|
|
|
|
|
|
|
|
|
|
return content.slice(-wrapperAttribute.length) == wrapperAttribute || content.slice(-wrapperTag.length) == wrapperTag
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 11:56:21 +02:00
|
|
|
|
function getAllIndicesOf(haystack, needle, caseSensitive, requestLineNum) {
|
|
|
|
|
var indices = []
|
2020-12-19 12:43:24 +01:00
|
|
|
|
if (needle.length === 0) {
|
2020-10-15 11:56:21 +02:00
|
|
|
|
return indices
|
|
|
|
|
}
|
2020-12-19 12:43:24 +01:00
|
|
|
|
var startIndex = 0, index = 0;
|
2020-10-15 11:56:21 +02:00
|
|
|
|
if (!caseSensitive) {
|
|
|
|
|
needle = needle.toLowerCase();
|
|
|
|
|
haystack = haystack.toLowerCase();
|
|
|
|
|
}
|
|
|
|
|
while (true) {
|
|
|
|
|
index = haystack.indexOf(needle, startIndex)
|
|
|
|
|
if (index === -1) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-10-20 08:28:50 +02:00
|
|
|
|
if (isDoubleExtraction(haystack.slice(index-10, index))) {
|
|
|
|
|
startIndex = index + needle.length + 1; // +1 for closing parenthesis
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-10-15 11:56:21 +02:00
|
|
|
|
if (requestLineNum) {
|
|
|
|
|
var position = cm.posFromIndex(index)
|
|
|
|
|
indices.push({
|
|
|
|
|
index: index,
|
|
|
|
|
editorPosition: position
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
indices.push(index)
|
|
|
|
|
}
|
|
|
|
|
startIndex = index + needle.length;
|
|
|
|
|
}
|
|
|
|
|
return indices;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-21 11:28:44 +02:00
|
|
|
|
function getNewSuggestionID() {
|
|
|
|
|
var randomID = getRandomID()
|
|
|
|
|
suggestionIDs.push(randomID)
|
|
|
|
|
return randomID
|
|
|
|
|
}
|
|
|
|
|
function consumeSuggestionID() {
|
|
|
|
|
return suggestionIDs.shift()
|
|
|
|
|
}
|
|
|
|
|
function resetSuggestionIDs() {
|
|
|
|
|
suggestionIDs = []
|
|
|
|
|
}
|
|
|
|
|
function getRandomID() {
|
|
|
|
|
return Math.random().toString(36).substr(2,9)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 11:56:21 +02:00
|
|
|
|
function getLineNumInArrayList(index, arrayToSearchInto) {
|
|
|
|
|
for (var lineNum = 0; lineNum < arrayToSearchInto.length; lineNum++) {
|
|
|
|
|
var newLineIndex = arrayToSearchInto[lineNum];
|
|
|
|
|
if (index < newLineIndex) {
|
|
|
|
|
return lineNum - 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-21 10:01:15 +02:00
|
|
|
|
function findBackClosestStartLine(tokens, i) {
|
|
|
|
|
if (tokens[i].map !== null) {
|
|
|
|
|
return tokens[i].map
|
|
|
|
|
}
|
|
|
|
|
var token
|
|
|
|
|
for (var j = i-1; j >= 0; j--) {
|
|
|
|
|
token = tokens[j]
|
|
|
|
|
if (token.map !== null) {
|
|
|
|
|
return token.map
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 11:56:21 +02:00
|
|
|
|
function parseDestinationValue(str, pos, max) {
|
2020-10-01 16:55:17 +02:00
|
|
|
|
var level = 0
|
|
|
|
|
var lines = 0
|
|
|
|
|
var code
|
|
|
|
|
var start = pos
|
|
|
|
|
var result = {
|
|
|
|
|
ok: false,
|
|
|
|
|
pos: 0,
|
|
|
|
|
lines: 0,
|
|
|
|
|
str: ''
|
|
|
|
|
};
|
|
|
|
|
while (pos < max) {
|
|
|
|
|
code = str.charCodeAt(pos);
|
|
|
|
|
|
|
|
|
|
// ascii control characters
|
|
|
|
|
if (code < 0x20 || code === 0x7F) { break; }
|
|
|
|
|
|
|
|
|
|
if (code === 0x5C /* \ */ && pos + 1 < max) {
|
|
|
|
|
pos += 2;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (code === 0x28 /* ( */) {
|
|
|
|
|
level++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (code === 0x29 /* ) */) {
|
|
|
|
|
level--;
|
2020-10-02 14:15:44 +02:00
|
|
|
|
if (level === 0) {
|
|
|
|
|
pos++;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-10-01 16:55:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (start === pos) { return result; }
|
|
|
|
|
if (level !== 0) { return result; }
|
|
|
|
|
|
2020-10-16 09:09:38 +02:00
|
|
|
|
result.str = str.slice(start, pos);
|
2020-10-01 16:55:17 +02:00
|
|
|
|
result.lines = lines;
|
|
|
|
|
result.pos = pos;
|
|
|
|
|
result.ok = true;
|
|
|
|
|
return result;
|
2020-10-18 16:36:51 +02:00
|
|
|
|
}
|