mirror of https://github.com/MISP/PyMISP
chg: Add i8n for pdfexport, without all the fonts in the main repo
parent
166ef3866d
commit
b8759673b9
|
@ -1,3 +1,6 @@
|
|||
[submodule "pymisp/data/misp-objects"]
|
||||
path = pymisp/data/misp-objects
|
||||
url = https://github.com/MISP/misp-objects
|
||||
[submodule "pymisp/tools/pdf_fonts"]
|
||||
path = pymisp/tools/pdf_fonts
|
||||
url = https://github.com/MISP/pdf_fonts
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 7ff222022e6ad99e11a201f62d57da4bff1337ee
|
|
@ -8,6 +8,7 @@ import pprint
|
|||
from io import BytesIO
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
if sys.version_info.major >= 3:
|
||||
from html import escape
|
||||
|
@ -21,13 +22,16 @@ logger = logging.getLogger('pymisp')
|
|||
# Potentially not installed imports
|
||||
try:
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.pdfbase.pdfmetrics import stringWidth
|
||||
from reportlab.pdfbase.pdfmetrics import stringWidth, registerFont, registerFontFamily
|
||||
from reportlab.pdfbase.pdfdoc import PDFDictionary, PDFInfo
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.utils import ImageReader
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib.fonts import addMapping
|
||||
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, PageBreak, Spacer, Table, TableStyle, Flowable, Image, Indenter
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, PageBreak, Spacer, Table, TableStyle, Flowable, Image, \
|
||||
Indenter
|
||||
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.units import mm
|
||||
|
@ -125,7 +129,7 @@ class Flowable_Tag(Flowable):
|
|||
|
||||
# Copy of pdfexport.py moduleconfig
|
||||
moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description",
|
||||
"Activate_galaxy_description"]
|
||||
"Activate_galaxy_description", "Activate_related_events", "Activate_internationalization_fonts"]
|
||||
|
||||
# == Row colors of the table (alternating) ==
|
||||
EVEN_COLOR = colors.whitesmoke
|
||||
|
@ -138,7 +142,6 @@ LINE_COLOR = colors.lightslategray
|
|||
LINE_THICKNESS = 0.75
|
||||
|
||||
# == Columns colors, aligment, fonts, space, size, width, heights ==
|
||||
# FIRST_COL_FONT_COLOR = colors.darkslateblue # Test purposes
|
||||
FIRST_COL_FONT_COLOR = colors.HexColor("#333333") # Same as GUI
|
||||
FIRST_COL_FONT = 'Helvetica-Bold'
|
||||
FIRST_COL_ALIGNEMENT = TA_CENTER
|
||||
|
@ -158,24 +161,23 @@ SMALL_COL2_ALIGMENT = TA_JUSTIFY
|
|||
|
||||
EXPORT_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
|
||||
COL_WIDTHS = ['25%', '75%'] # colWidths='*' # Not documented but does exist
|
||||
# COL_WIDTHS = ['20%', '80%'] # colWidths='*' # Not documented but does exist
|
||||
ROW_HEIGHT = 5 * mm # 4.5 * mm (a bit too short to allow vertical align TODO : Fix it)
|
||||
ROW_HEIGHT_FOR_TAGS = 4 * mm # 4.5 * mm (a bit too short to allow vertical align TODO : Fix it)
|
||||
|
||||
# == Whole document margins and size ==
|
||||
PAGESIZE = A4 # (140 * mm, 216 * mm) # width, height
|
||||
PAGESIZE = A4 # (140 * mm, 216 * mm) # width, height
|
||||
BASE_MARGIN = 5 * mm # Create a list here to specify each row separately
|
||||
INDENT_SIZE = 6 * mm # The Indentation of attribute from object, etc.
|
||||
INDENT_SIZE_HEADING = 3 * mm # The Indentation of attribute from object, etc.
|
||||
INDENT_SIZE = 6 * mm # The Indentation of attribute from object, etc.
|
||||
INDENT_SIZE_HEADING = 3 * mm # The Indentation of attribute from object, etc.
|
||||
|
||||
# == Parameters for error handling for content too long to fit on a page ==
|
||||
FRAME_MAX_HEIGHT = 200*mm # 500 # Ad hoc value for a A4 page
|
||||
FRAME_MAX_WIDTH = 145*mm - INDENT_SIZE # 356
|
||||
FRAME_MAX_HEIGHT = 200 * mm # 500 # Ad hoc value for a A4 page
|
||||
FRAME_MAX_WIDTH = 145 * mm - INDENT_SIZE # 356
|
||||
STR_TOO_LONG_WARNING = "<br/><b><font color=red>[Too long to fit on a single page. Cropped]</font></b>"
|
||||
|
||||
# == Parameters for error handling for image too big to fit on a page ==
|
||||
FRAME_PICTURE_MAX_HEIGHT = 200*mm # 195 * mm
|
||||
FRAME_PICTURE_MAX_WIDTH = 145*mm - INDENT_SIZE # 88 * mm
|
||||
FRAME_PICTURE_MAX_HEIGHT = 200 * mm # 195 * mm
|
||||
FRAME_PICTURE_MAX_WIDTH = 145 * mm - INDENT_SIZE # 88 * mm
|
||||
|
||||
# == Parameters for links management ==
|
||||
LINK_TYPE = "link" # Name of the type that define 'good' links
|
||||
|
@ -190,6 +192,7 @@ BAD_LINK_COLOR = 'red'
|
|||
LOW_THREAT_COLOR = 'green'
|
||||
MEDIUM_THREAT_COLOR = 'orange'
|
||||
HIGH_THREAT_COLOR = 'red'
|
||||
EXTERNAL_ANALYSIS_PREFIX = "<i>External analysis from an attribute : </i>"
|
||||
|
||||
# == Parameters for improvement of event's metadata ==
|
||||
|
||||
|
@ -209,9 +212,9 @@ MISC_SIGHT_COLOR = 'orange'
|
|||
|
||||
# == Parameters for galaxies ==
|
||||
DO_SMALL_GALAXIES = True
|
||||
FIRST_LEVEL_GALAXY_WIDTHS = ["15%","85%"]
|
||||
SECOND_LEVEL_GALAXY_WIDTHS = ["20%","80%"]
|
||||
CLUSTER_COLORS = [0] # or 1
|
||||
FIRST_LEVEL_GALAXY_WIDTHS = ["15%", "85%"]
|
||||
SECOND_LEVEL_GALAXY_WIDTHS = ["20%", "80%"]
|
||||
CLUSTER_COLORS = [0] # or 1
|
||||
OFFSET = 1
|
||||
|
||||
########################################################################
|
||||
|
@ -258,7 +261,8 @@ def uuid_to_url(baseurl, uuid):
|
|||
return baseurl + "events/view/" + uuid
|
||||
|
||||
|
||||
def create_flowable_table_from_data(data, col_w=COL_WIDTHS, color_alternation=None, line_alternation=None, galaxy_colors=False):
|
||||
def create_flowable_table_from_data(data, col_w=COL_WIDTHS, color_alternation=None, line_alternation=None,
|
||||
galaxy_colors=False):
|
||||
'''
|
||||
Given a list of flowables items (2D/list of list), creates a Table with styles.
|
||||
:param data: list of list of items (flowables is better)
|
||||
|
@ -272,8 +276,8 @@ def create_flowable_table_from_data(data, col_w=COL_WIDTHS, color_alternation=No
|
|||
# rowHeights=ROW_HEIGHT if you want a fixed height. /!\ Problems with paragraphs that are spreading everywhere
|
||||
|
||||
# Create styles and set parameters
|
||||
alternate_colors_style = alternate_colors_style_generator(data,color_alternation, galaxy_colors)
|
||||
lines_style = lines_style_generator(data,line_alternation)
|
||||
alternate_colors_style = alternate_colors_style_generator(data, color_alternation, galaxy_colors)
|
||||
lines_style = lines_style_generator(data, line_alternation)
|
||||
general_style = general_style_generator()
|
||||
|
||||
# Make the table nicer
|
||||
|
@ -310,7 +314,7 @@ def alternate_colors_style_generator(data, color_alternation, galaxy_colors=True
|
|||
|
||||
# For each line, generate a tuple giving to a line a color
|
||||
for each in range(data_len):
|
||||
if color_alternation[each%len(color_alternation)] % 2 == 0:
|
||||
if color_alternation[each % len(color_alternation)] % 2 == 0:
|
||||
bg_color = EVEN_COLOR if not galaxy_colors else EVEN_COLOR_GALAXY
|
||||
else:
|
||||
bg_color = ODD_COLOR if not galaxy_colors else ODD_COLOR_GALAXY
|
||||
|
@ -338,7 +342,7 @@ def lines_style_generator(data, line_alternation):
|
|||
|
||||
# Last line
|
||||
lines_list.append(('LINEBELOW', (0, len(data) - 1), (-1, len(data) - 1), LINE_THICKNESS, LINE_COLOR))
|
||||
elif line_alternation == [] :
|
||||
elif line_alternation == []:
|
||||
# Do nothing
|
||||
return lines_list
|
||||
else:
|
||||
|
@ -347,13 +351,13 @@ def lines_style_generator(data, line_alternation):
|
|||
|
||||
# For each line, generate a tuple giving to a line a color
|
||||
for each in range(data_len):
|
||||
if each == 0 or line_alternation[each%len(line_alternation)] != line_alternation[(each-1)%len(line_alternation)]:
|
||||
if each == 0 or line_alternation[each % len(line_alternation)] != line_alternation[
|
||||
(each - 1) % len(line_alternation)]:
|
||||
lines_list.append(('LINEABOVE', (0, each), (-1, each), LINE_THICKNESS, LINE_COLOR))
|
||||
|
||||
# Last line
|
||||
lines_list.append(('LINEBELOW', (0, len(data) - 1), (-1, len(data) - 1), LINE_THICKNESS, LINE_COLOR))
|
||||
|
||||
|
||||
return lines_list
|
||||
|
||||
|
||||
|
@ -375,6 +379,36 @@ def general_style_generator():
|
|||
return lines_list
|
||||
|
||||
|
||||
def internationalize_font():
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/tools"
|
||||
|
||||
global FIRST_COL_FONT
|
||||
global SECOND_COL_FONT
|
||||
|
||||
''' # Available fonts :
|
||||
NotoSansCJKtc - DemiLight.ttf
|
||||
NotoSansCJKtc - Regular.ttf
|
||||
NotoSansCJKtc - Black.ttf
|
||||
NotoSansCJKtc - Light.ttf
|
||||
NotoSansCJKtc - Thin.ttf
|
||||
NotoSansCJKtc - Bold.ttf
|
||||
NotoSansCJKtc - Medium.ttf
|
||||
'''
|
||||
|
||||
noto_bold = BASE_DIR + "/pdf_fonts/Noto_TTF/NotoSansCJKtc-Bold.ttf"
|
||||
noto = BASE_DIR + "/pdf_fonts/Noto_TTF/NotoSansCJKtc-DemiLight.ttf"
|
||||
|
||||
if os.path.isfile(noto_bold) and os.path.isfile(noto):
|
||||
registerFont(TTFont("Noto", noto))
|
||||
registerFont(TTFont("Noto-bold", noto_bold))
|
||||
|
||||
FIRST_COL_FONT = 'Noto-bold'
|
||||
SECOND_COL_FONT = 'Noto'
|
||||
else:
|
||||
logger.error(
|
||||
"Trying to load a custom (internationalization) font, unable to access the file : " + noto_bold)
|
||||
|
||||
|
||||
def get_table_styles():
|
||||
'''
|
||||
Create and returns the two mains styles for the columns of the document.
|
||||
|
@ -426,6 +460,7 @@ def get_clusters_table_styles():
|
|||
|
||||
return custom_body_style_col_1, custom_body_style_col_2
|
||||
|
||||
|
||||
########################################################################
|
||||
# Checks
|
||||
|
||||
|
@ -474,8 +509,8 @@ class Value_Formatter():
|
|||
# ----------------------------------------------------------------------
|
||||
########################################################################
|
||||
# General attribut formater
|
||||
def get_col1_paragraph(self, dirty_string, do_small=False):
|
||||
if do_small :
|
||||
def get_col1_paragraph(self, dirty_string, do_small=False):
|
||||
if do_small:
|
||||
return self.get_unoverflowable_paragraph(dirty_string, self.col1_small_style, do_small=do_small)
|
||||
return self.get_unoverflowable_paragraph(dirty_string, self.col1_style, do_small=do_small)
|
||||
|
||||
|
@ -494,13 +529,12 @@ class Value_Formatter():
|
|||
else:
|
||||
sanitized_str = dirty_string
|
||||
|
||||
if curr_style is None :
|
||||
if do_small :
|
||||
if curr_style is None:
|
||||
if do_small:
|
||||
curr_style = self.col2_small_style
|
||||
else :
|
||||
else:
|
||||
curr_style = self.col2_style
|
||||
|
||||
|
||||
# Get the space that the paragraph needs to be printed
|
||||
w, h = Paragraph(sanitized_str, curr_style).wrap(FRAME_MAX_WIDTH, FRAME_MAX_HEIGHT)
|
||||
|
||||
|
@ -780,7 +814,8 @@ class Value_Formatter():
|
|||
if is_safe_dict_attribute(misp_galaxy, item[1]):
|
||||
return self.get_unoverflowable_paragraph(safe_string(misp_galaxy[item[1]])
|
||||
+ " <i>from</i> " + safe_string(misp_galaxy[item[3]]) + ":"
|
||||
+ safe_string(misp_galaxy[item[4]]), do_escape_string=False, do_small=True)
|
||||
+ safe_string(misp_galaxy[item[4]]), do_escape_string=False,
|
||||
do_small=True)
|
||||
return self.get_unoverflowable_paragraph(item[2], do_small=True)
|
||||
|
||||
def get_galaxy_cluster_name_value(self, misp_cluster, do_small=False):
|
||||
|
@ -790,19 +825,20 @@ class Value_Formatter():
|
|||
if is_safe_dict_attribute(misp_cluster, item[1]):
|
||||
tmp_text += safe_string(misp_cluster[item[1]])
|
||||
|
||||
#if is_safe_dict_attribute(misp_cluster, item[3]) :
|
||||
# tmp_text += "<br/><i>Source :</i> " + misp_cluster[item[3]]
|
||||
# if is_safe_dict_attribute(misp_cluster, item[3]) :
|
||||
# tmp_text += "<br/><i>Source :</i> " + misp_cluster[item[3]]
|
||||
|
||||
if is_safe_dict_attribute(misp_cluster, item[4]) and is_safe_dict_attribute(misp_cluster[item[4]], item[5]):
|
||||
tmp_text += " <br/><i>Synonyms :</i> "
|
||||
for i, synonyme in enumerate(misp_cluster[item[4]][item[5]]) :
|
||||
if i != 0 :
|
||||
for i, synonyme in enumerate(misp_cluster[item[4]][item[5]]):
|
||||
if i != 0:
|
||||
tmp_text += " / "
|
||||
tmp_text += safe_string(synonyme)
|
||||
|
||||
return self.get_unoverflowable_paragraph(tmp_text, do_escape_string=False, do_small=do_small)
|
||||
return self.get_unoverflowable_paragraph(item[2], do_small=do_small)
|
||||
|
||||
|
||||
class Event_Metadata():
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
@ -884,6 +920,10 @@ class Event_Metadata():
|
|||
|
||||
flowable_table.append(create_flowable_table_from_data(data))
|
||||
|
||||
# Correlation
|
||||
item = ["Related Events", 'RelatedEvent', "None"]
|
||||
if is_safe_attribute_table(misp_event, item[1]) and is_in_config(self.config, 4):
|
||||
flowable_table += self.get_correlation_values(misp_event, item)
|
||||
|
||||
# Galaxies
|
||||
item = ["Related Galaxies", 'Galaxy', "None"]
|
||||
|
@ -894,6 +934,36 @@ class Event_Metadata():
|
|||
|
||||
return flowable_table
|
||||
|
||||
def create_reduced_flowable_table_from_event(self, misp_event):
|
||||
'''
|
||||
Returns Table presenting a MISP event
|
||||
:param misp_event: A misp event (complete or not)
|
||||
:return: a table that can be added to a pdf
|
||||
'''
|
||||
|
||||
data = []
|
||||
flowable_table = []
|
||||
|
||||
# Manual addition
|
||||
# UUID
|
||||
item = ["UUID", 'uuid', "None"]
|
||||
data.append([self.value_formatter.get_col1_paragraph(item[0]),
|
||||
self.value_formatter.get_value_link_to_event(misp_event, item)])
|
||||
|
||||
# Info
|
||||
item = ["Info", 'info', "None"]
|
||||
data.append([self.value_formatter.get_col1_paragraph(item[0]),
|
||||
self.value_formatter.get_value_link_to_event(misp_event, item)])
|
||||
|
||||
# Timestamp
|
||||
item = ["Event date", 'timestamp', "None"]
|
||||
data.append([self.value_formatter.get_col1_paragraph(item[0]),
|
||||
self.value_formatter.get_timestamp_value(misp_event, item)])
|
||||
|
||||
flowable_table.append(create_flowable_table_from_data(data))
|
||||
|
||||
return flowable_table
|
||||
|
||||
def create_flowable_description_from_event(self, misp_event):
|
||||
'''
|
||||
Returns a Paragraph presenting a MISP event
|
||||
|
@ -988,6 +1058,14 @@ class Event_Metadata():
|
|||
|
||||
text += " associated objects."
|
||||
|
||||
curr_attributes = Attributes(self.config, self.value_formatter)
|
||||
tmp_text = curr_attributes.get_external_analysis(misp_event)
|
||||
|
||||
if tmp_text != "":
|
||||
text += "<br/>"
|
||||
text += tmp_text
|
||||
text += "<br/>"
|
||||
|
||||
'''
|
||||
For more information on the event, please consult the rest of the document
|
||||
'''
|
||||
|
@ -998,6 +1076,33 @@ class Event_Metadata():
|
|||
|
||||
return Paragraph(text, description_style)
|
||||
|
||||
def get_correlation_values(self, misp_event, item):
|
||||
'''
|
||||
Returns a flowable paragraph to add to the pdf given the misp_event correlated events
|
||||
:param misp_event: A misp event with or without "RelatedEvent" attributes
|
||||
:param item: as defined in class definition
|
||||
:param col2_style: style to be applied on the returned paragraph
|
||||
:return: a Paragraph to add in the pdf, regarding the values of "RelatedEvent"
|
||||
'''
|
||||
flowable_table = []
|
||||
|
||||
flowable_table.append(PageBreak())
|
||||
flowable_table.append(Paragraph("Related Events", self.sample_style_sheet['Heading3']))
|
||||
|
||||
if is_safe_attribute_table(misp_event, item[1]):
|
||||
for i, evt in enumerate(getattr(misp_event, item[1])):
|
||||
flowable_table.append(Indenter(left=INDENT_SIZE_HEADING))
|
||||
flowable_table.append(
|
||||
Paragraph("Related Event #" + str(i + OFFSET), self.sample_style_sheet['Heading4']))
|
||||
flowable_table.append(Indenter(left=-INDENT_SIZE_HEADING))
|
||||
|
||||
flowable_table += self.create_reduced_flowable_table_from_event(evt)
|
||||
i += 1
|
||||
else:
|
||||
return flowable_table.append(self.value_formatter.get_unoverflowable_paragraph(item[2]))
|
||||
|
||||
return flowable_table
|
||||
|
||||
|
||||
class Attributes():
|
||||
|
||||
|
@ -1024,7 +1129,7 @@ class Attributes():
|
|||
for item in getattr(misp_event, "Attribute"):
|
||||
# you can use a spacer instead of title to separate paragraph: flowable_table.append(Spacer(1, 5 * mm))
|
||||
flowable_table.append(Indenter(left=INDENT_SIZE_HEADING))
|
||||
flowable_table.append(Paragraph("Attribute #" + str(i+OFFSET), self.sample_style_sheet['Heading4']))
|
||||
flowable_table.append(Paragraph("Attribute #" + str(i + OFFSET), self.sample_style_sheet['Heading4']))
|
||||
flowable_table.append(Indenter(left=-INDENT_SIZE_HEADING))
|
||||
|
||||
flowable_table += self.create_flowable_table_from_one_attribute(item)
|
||||
|
@ -1098,10 +1203,9 @@ class Attributes():
|
|||
|
||||
flowable_table.append(create_flowable_table_from_data(data))
|
||||
|
||||
|
||||
# Galaxies
|
||||
item = ["Related Galaxies", 'Galaxy', "None"]
|
||||
if is_safe_attribute_table(misp_attribute, item[1]) and is_in_config(self.config, 3) :
|
||||
if is_safe_attribute_table(misp_attribute, item[1]) and is_in_config(self.config, 3):
|
||||
curr_Galaxy = Galaxy(self.config, self.value_formatter)
|
||||
flowable_table.append(Indenter(left=INDENT_SIZE))
|
||||
flowable_table += curr_Galaxy.get_galaxy_value(misp_attribute, item)
|
||||
|
@ -1109,6 +1213,27 @@ class Attributes():
|
|||
|
||||
return flowable_table
|
||||
|
||||
def get_external_analysis(self, misp_event):
|
||||
'''
|
||||
Returns a string representing the list of external analysis comments of a misp event.
|
||||
:param misp_event: A misp event
|
||||
:return: a table of flowables
|
||||
'''
|
||||
text = ""
|
||||
|
||||
if is_safe_attribute_table(misp_event, "Attribute"):
|
||||
# There is some attributes for this object
|
||||
for attribute in getattr(misp_event, "Attribute"):
|
||||
# If the current event is an external analysis and a comment
|
||||
if is_safe_attribute(attribute, "value") and is_safe_attribute(attribute,
|
||||
"category") and is_safe_attribute(
|
||||
attribute, "type") and getattr(attribute, "category") == "External analysis" and getattr(
|
||||
attribute, "type") == "comment":
|
||||
# We add it to the description
|
||||
text += "<br/>" + EXTERNAL_ANALYSIS_PREFIX + safe_string(getattr(attribute, "value"))
|
||||
|
||||
return text
|
||||
|
||||
|
||||
class Tags():
|
||||
|
||||
|
@ -1215,7 +1340,6 @@ class Sightings():
|
|||
return answer_sighting
|
||||
|
||||
|
||||
|
||||
class Object():
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
@ -1244,7 +1368,7 @@ class Object():
|
|||
for item in getattr(misp_event, "Object"):
|
||||
# you can use a spacer instead of title to separate paragraph: flowable_table.append(Spacer(1, 5 * mm))
|
||||
flowable_table.append(Indenter(left=INDENT_SIZE_HEADING))
|
||||
flowable_table.append(Paragraph("Object #" + str(i+OFFSET), self.sample_style_sheet['Heading3']))
|
||||
flowable_table.append(Paragraph("Object #" + str(i + OFFSET), self.sample_style_sheet['Heading3']))
|
||||
flowable_table.append(Indenter(left=-INDENT_SIZE_HEADING))
|
||||
flowable_table += self.create_flowable_table_from_one_object(item, config)
|
||||
i += 1
|
||||
|
@ -1254,7 +1378,6 @@ class Object():
|
|||
|
||||
return flowable_table
|
||||
|
||||
|
||||
def create_flowable_table_from_one_object(self, misp_object, config=None):
|
||||
'''
|
||||
Returns a table (flowable) representing the object
|
||||
|
@ -1284,7 +1407,8 @@ class Object():
|
|||
|
||||
# Timestamp
|
||||
item = ["Object date", 'timestamp', "None"]
|
||||
data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_timestamp_value(misp_object, item)])
|
||||
data.append([self.value_formatter.get_col1_paragraph(item[0]),
|
||||
self.value_formatter.get_timestamp_value(misp_object, item)])
|
||||
|
||||
# Transform list of value in a table
|
||||
data = [create_flowable_table_from_data(data)]
|
||||
|
@ -1328,12 +1452,12 @@ class Galaxy():
|
|||
# Galaxies
|
||||
# item = ["Related Galaxies", 'Galaxy', "None"]
|
||||
if is_safe_attribute_table(misp_event, item[1]) and is_in_config(self.config, 3):
|
||||
galaxy_title = Paragraph(item[0], self.sample_style_sheet['Heading5'])
|
||||
galaxy_title = Paragraph(safe_string(item[0]), self.sample_style_sheet['Heading5'])
|
||||
flowable_table.append(Indenter(left=INDENT_SIZE_HEADING))
|
||||
flowable_table.append(galaxy_title)
|
||||
flowable_table.append(Indenter(left=-INDENT_SIZE_HEADING))
|
||||
flowable_table += self.create_flowable_table_from_galaxies(misp_event)
|
||||
else :
|
||||
else:
|
||||
flowable_table.append(self.value_formatter.get_unoverflowable_paragraph(item[2]))
|
||||
|
||||
return flowable_table
|
||||
|
@ -1348,7 +1472,8 @@ class Galaxy():
|
|||
scheme_alternation = []
|
||||
curr_color = 0
|
||||
i = 0
|
||||
|
||||
small_title_style = ParagraphStyle(name='Column_1', parent=self.sample_style_sheet['Heading6'],
|
||||
fontName=FIRST_COL_FONT, alignment=TA_LEFT)
|
||||
|
||||
if is_safe_attribute_table(misp_event, "Galaxy"):
|
||||
# There is some galaxies for this object
|
||||
|
@ -1356,8 +1481,8 @@ class Galaxy():
|
|||
for curr_galaxy in getattr(misp_event, "Galaxy"):
|
||||
# For each galaxy of the misp object
|
||||
|
||||
txt_title = "Galaxy #" + str(i+OFFSET) + " - " + safe_string(curr_galaxy["name"])
|
||||
galaxy_title = Paragraph(txt_title, self.sample_style_sheet['Heading6'])
|
||||
txt_title = "Galaxy #" + str(i + OFFSET) + " - " + safe_string(curr_galaxy["name"])
|
||||
galaxy_title = Paragraph(txt_title, small_title_style)
|
||||
flowable_table.append(Indenter(left=INDENT_SIZE_HEADING))
|
||||
flowable_table.append(galaxy_title)
|
||||
flowable_table.append(Indenter(left=-INDENT_SIZE_HEADING))
|
||||
|
@ -1403,7 +1528,8 @@ class Galaxy():
|
|||
item = ["Description", 'description', "None"]
|
||||
if is_safe_dict_attribute(misp_galaxy, item[1]):
|
||||
data.append([self.value_formatter.get_col1_paragraph(item[0], do_small=DO_SMALL_GALAXIES),
|
||||
self.value_formatter.get_unoverflowable_paragraph(misp_galaxy[item[1]], do_small=DO_SMALL_GALAXIES)])
|
||||
self.value_formatter.get_unoverflowable_paragraph(misp_galaxy[item[1]],
|
||||
do_small=DO_SMALL_GALAXIES)])
|
||||
nb_added_item += 1
|
||||
|
||||
flowable_table = []
|
||||
|
@ -1412,7 +1538,6 @@ class Galaxy():
|
|||
return flowable_table, nb_added_item
|
||||
|
||||
|
||||
|
||||
class Galaxy_cluster():
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
@ -1435,26 +1560,29 @@ class Galaxy_cluster():
|
|||
if is_safe_dict_attribute(misp_galaxy, "GalaxyCluster"):
|
||||
# There is some clusters for this object
|
||||
for i, curr_cluster in enumerate(misp_galaxy["GalaxyCluster"]):
|
||||
|
||||
# If title is needed :
|
||||
# galaxy_title = [Paragraph("Cluster #" + str(i), self.sample_style_sheet['Heading6'])]
|
||||
# data.append(galaxy_title)
|
||||
|
||||
|
||||
item[0] = "Cluster #" + str(i + OFFSET)
|
||||
|
||||
# For each cluster
|
||||
tmp_data = self.create_flowable_table_from_one_galaxy_cluster(curr_cluster)
|
||||
tmp_flowable_table = []
|
||||
tmp_flowable_table.append(create_flowable_table_from_data(tmp_data, col_w=SECOND_LEVEL_GALAXY_WIDTHS, color_alternation = CLUSTER_COLORS, line_alternation=[], galaxy_colors=True))
|
||||
data.append([self.value_formatter.get_col1_paragraph(item[0], do_small=DO_SMALL_GALAXIES), tmp_flowable_table]) # Cluster #X - 3 lines
|
||||
tmp_flowable_table.append(create_flowable_table_from_data(tmp_data, col_w=SECOND_LEVEL_GALAXY_WIDTHS,
|
||||
color_alternation=CLUSTER_COLORS,
|
||||
line_alternation=[], galaxy_colors=True))
|
||||
data.append([self.value_formatter.get_col1_paragraph(item[0], do_small=DO_SMALL_GALAXIES),
|
||||
tmp_flowable_table]) # Cluster #X - 3 lines
|
||||
|
||||
else:
|
||||
# No galaxies for this object
|
||||
data = [self.value_formatter.get_unoverflowable_paragraph("No galaxy cluster", do_small=DO_SMALL_GALAXIES)]
|
||||
|
||||
flowable_table = []
|
||||
flowable_table.append(create_flowable_table_from_data(data, col_w=FIRST_LEVEL_GALAXY_WIDTHS, color_alternation = CLUSTER_COLORS, galaxy_colors=True))
|
||||
flowable_table.append(
|
||||
create_flowable_table_from_data(data, col_w=FIRST_LEVEL_GALAXY_WIDTHS, color_alternation=CLUSTER_COLORS,
|
||||
galaxy_colors=True))
|
||||
|
||||
return flowable_table
|
||||
|
||||
|
@ -1469,13 +1597,13 @@ class Galaxy_cluster():
|
|||
# Name
|
||||
item = ["Name", 'name', "None"]
|
||||
data.append([self.value_formatter.get_col1_paragraph(item[0], do_small=True),
|
||||
self.value_formatter.get_galaxy_cluster_name_value(misp_cluster, do_small=True)])
|
||||
self.value_formatter.get_galaxy_cluster_name_value(misp_cluster, do_small=True)])
|
||||
|
||||
if misp_cluster['value'] != misp_cluster['description'] : # Prevent name that are same as description
|
||||
if misp_cluster['value'] != misp_cluster['description']: # Prevent name that are same as description
|
||||
# Description
|
||||
item = ["Description", 'description', "None"]
|
||||
data.append([self.value_formatter.get_col1_paragraph(item[0], do_small=True),
|
||||
self.value_formatter.get_unoverflowable_paragraph(misp_cluster[item[1]], do_small=True)])
|
||||
self.value_formatter.get_unoverflowable_paragraph(misp_cluster[item[1]], do_small=True)])
|
||||
|
||||
# Refs ?
|
||||
# item = ["Description", 'description', "None"]
|
||||
|
@ -1572,7 +1700,8 @@ def collect_parts(misp_event, config=None):
|
|||
curr_val_f = Value_Formatter(config, col1_style, col2_style, col1_small_style, col2_small_style)
|
||||
|
||||
# Create stuff
|
||||
title_style = ParagraphStyle(name='Column_1', parent=sample_style_sheet['Heading1'], alignment=TA_CENTER)
|
||||
title_style = ParagraphStyle(name='Column_1', parent=sample_style_sheet['Heading1'], fontName=FIRST_COL_FONT,
|
||||
alignment=TA_CENTER)
|
||||
title = curr_val_f.get_value_link_to_event(misp_event, ["Info", 'info', "None"], title_style, color=False)
|
||||
# Add all parts to final PDF
|
||||
flowables.append(title)
|
||||
|
@ -1593,7 +1722,7 @@ def collect_parts(misp_event, config=None):
|
|||
flowables.append(subtitle)
|
||||
flowables += table_general_metainformation
|
||||
|
||||
if is_safe_attribute_table(misp_event, "Attribute") :
|
||||
if is_safe_attribute_table(misp_event, "Attribute"):
|
||||
flowables.append(PageBreak())
|
||||
|
||||
event_attributes_title = Paragraph("Attributes", sample_style_sheet['Heading2'])
|
||||
|
@ -1601,7 +1730,7 @@ def collect_parts(misp_event, config=None):
|
|||
flowables.append(event_attributes_title)
|
||||
flowables += table_direct_attributes
|
||||
|
||||
if is_safe_attribute_table(misp_event, "Object") :
|
||||
if is_safe_attribute_table(misp_event, "Object"):
|
||||
flowables.append(PageBreak())
|
||||
|
||||
event_objects_title = Paragraph("Objects", sample_style_sheet['Heading2'])
|
||||
|
@ -1647,6 +1776,10 @@ def convert_event_in_pdf_buffer(misp_event, config=None):
|
|||
# Create a document buffer
|
||||
pdf_buffer = BytesIO()
|
||||
|
||||
if is_in_config(config, 5): # We want internationalization
|
||||
logger.info("Internationalization of fonts during pdf export activated. CJK-fonts supported.")
|
||||
internationalize_font()
|
||||
|
||||
# DEBUG / TO DELETE : curr_document = SimpleDocTemplate('myfile.pdf')
|
||||
curr_document = SimpleDocTemplate(pdf_buffer,
|
||||
pagesize=PAGESIZE,
|
||||
|
|
3
setup.py
3
setup.py
|
@ -59,5 +59,6 @@ setup(
|
|||
'data/misp-objects/schema_objects.json',
|
||||
'data/misp-objects/schema_relationships.json',
|
||||
'data/misp-objects/objects/*/definition.json',
|
||||
'data/misp-objects/relationships/definition.json']},
|
||||
'data/misp-objects/relationships/definition.json',
|
||||
'tools/pdf_fonts/Noto_TTF/*']},
|
||||
)
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -33,7 +33,7 @@ class TestMISPEvent(unittest.TestCase):
|
|||
self.storage_folder = self.root + "reportlab_testoutputs/"
|
||||
self.storage_image_folder = self.root + "reportlab_test_image_outputs/"
|
||||
self.moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description",
|
||||
"Activate_galaxy_description"]
|
||||
"Activate_galaxy_description", "Activate_related_events", "Activate_internationalization_fonts"]
|
||||
|
||||
|
||||
def init_event(self):
|
||||
|
@ -244,6 +244,58 @@ class TestMISPEvent(unittest.TestCase):
|
|||
reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config),
|
||||
self.storage_folder + "galaxy_1.pdf")
|
||||
|
||||
def test_related_events(self):
|
||||
if self.check_python_2():
|
||||
self.assertTrue(True)
|
||||
else:
|
||||
config = {}
|
||||
config[self.moduleconfig[0]] = "http://localhost:8080"
|
||||
config[self.moduleconfig[1]] = "My Wonderful CERT"
|
||||
config[self.moduleconfig[2]] = True
|
||||
config[self.moduleconfig[3]] = True
|
||||
config[self.moduleconfig[4]] = True
|
||||
|
||||
self.init_event()
|
||||
self.mispevent.load_file(self.test_folder + 'galaxy_1.json')
|
||||
reportlab_generator.register_value_to_file(
|
||||
reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config),
|
||||
self.storage_folder + "related_events.pdf")
|
||||
|
||||
def test_related_events_too_simple(self):
|
||||
if self.check_python_2():
|
||||
self.assertTrue(True)
|
||||
else:
|
||||
config = {}
|
||||
config[self.moduleconfig[0]] = "http://localhost:8080"
|
||||
config[self.moduleconfig[1]] = "My Wonderful CERT"
|
||||
config[self.moduleconfig[2]] = True
|
||||
config[self.moduleconfig[3]] = True
|
||||
config[self.moduleconfig[4]] = True
|
||||
|
||||
self.init_event()
|
||||
self.mispevent.load_file(self.test_folder + 'to_delete1.json')
|
||||
reportlab_generator.register_value_to_file(
|
||||
reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config),
|
||||
self.storage_folder + "related_events_no_related.pdf")
|
||||
|
||||
def test_utf(self):
|
||||
if self.check_python_2():
|
||||
self.assertTrue(True)
|
||||
else:
|
||||
config = {}
|
||||
config[self.moduleconfig[0]] = "http://localhost:8080"
|
||||
config[self.moduleconfig[1]] = "My Wonderful CERT"
|
||||
config[self.moduleconfig[2]] = True
|
||||
config[self.moduleconfig[3]] = True
|
||||
config[self.moduleconfig[4]] = True
|
||||
config[self.moduleconfig[5]] = True
|
||||
|
||||
self.init_event()
|
||||
self.mispevent.load_file(self.test_folder + 'japanese_test.json')
|
||||
reportlab_generator.register_value_to_file(
|
||||
reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config),
|
||||
self.storage_folder + "japanese_test.pdf")
|
||||
|
||||
def test_batch_image_events(self):
|
||||
# Test case ONLY for manual testing. Needs to download a full list of image events !
|
||||
|
||||
|
@ -315,6 +367,8 @@ class TestMISPEvent(unittest.TestCase):
|
|||
config[self.moduleconfig[1]] = "My Wonderful CERT"
|
||||
config[self.moduleconfig[2]] = True
|
||||
config[self.moduleconfig[3]] = True
|
||||
config[self.moduleconfig[4]] = True
|
||||
config[self.moduleconfig[5]] = True
|
||||
|
||||
|
||||
file_nb = str(len(os.listdir(self.test_batch_folder)))
|
||||
|
|
Loading…
Reference in New Issue