chg: Add i8n for pdfexport, without all the fonts in the main repo

pull/362/head
Raphaël Vinot 2019-03-04 16:06:38 -08:00
parent 166ef3866d
commit b8759673b9
6 changed files with 408 additions and 60 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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)))