From 9adff0b57438b102290e568d19867a6861300f62 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Thu, 28 Feb 2019 10:58:49 +0100 Subject: [PATCH 01/10] chg: [exportPDF] add basic handling of clusters --- pymisp/tools/reportlab_generator.py | 409 +++++--- tests/reportlab_testfiles/galaxy_1.json | 1250 +++++++++++++++++++++++ tests/test_reportlab.py | 114 ++- 3 files changed, 1585 insertions(+), 188 deletions(-) create mode 100644 tests/reportlab_testfiles/galaxy_1.json diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index ec9d44c..9cf748d 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -10,6 +10,8 @@ from functools import partial import sys +from orca.cmdnames import TABLE_CELL_FIRST + if sys.version_info.major >= 3: from html import escape # import PIL @@ -125,7 +127,8 @@ class Flowable_Tag(Flowable): # Copy of pdfexport.py moduleconfig -moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description"] +moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description", + "Activate_galaxy_description"] # == Row colors of the table (alternating) == EVEN_COLOR = colors.whitesmoke @@ -309,6 +312,71 @@ def get_table_styles(): return custom_body_style_col_1, custom_body_style_col_2 +######################################################################## +# Checks + +def safe_string(bad_str): + return escape(str(bad_str)) + +def is_safe_attribute(curr_object, attribute_name): + return hasattr(curr_object, attribute_name) and getattr(curr_object, attribute_name) is not None and getattr( + curr_object, attribute_name) != "" + + +def is_safe_dict_attribute(curr_object, attribute_name): + return attribute_name in curr_object and curr_object[attribute_name] is not None and curr_object[ + attribute_name] != "" + + +def is_safe_attribute_table(curr_object, attribute_name): + return hasattr(curr_object, attribute_name) and getattr(curr_object, attribute_name) is not None and getattr( + curr_object, attribute_name) != [] + +######################################################################## +# General attribut formater + +def get_unoverflowable_paragraph(dirty_string, curr_style, do_escape_string=True): + ''' + Create a paragraph that can fit on a cell of one page. Mostly hardcoded values. + This method can be improved (get the exact size of the current frame, and limit the paragraph to this size.) + This might be worst look at KeepInFrame (which hasn't went well so far) + :param do_escape_string: Activate the escaping (may be useful to add inline HTML, e.g. hyperlinks) + :param dirty_string: String to transform + :param curr_style: Style to apply to the returned paragraph + :return: + ''' + if do_escape_string: + sanitized_str = str(escape(str(dirty_string))) + else: + sanitized_str = dirty_string + + # Get the space that the paragraph needs to be printed + w, h = Paragraph(sanitized_str, curr_style).wrap(FRAME_MAX_WIDTH, FRAME_MAX_HEIGHT) + + # If there is enough space, directly send back the sanitized paragraph + if w <= FRAME_MAX_WIDTH and h <= FRAME_MAX_HEIGHT: + answer_paragraph = Paragraph(sanitized_str, curr_style) + else: + # Otherwise, cut the content to fit the paragraph (Dichotomy) + max_carac_amount = int((FRAME_MAX_HEIGHT / (h * 1.0)) * len(sanitized_str)) + + i = 0 + MAX_ITERATION = 10 + limited_string = "" + while (w > FRAME_MAX_WIDTH or h > FRAME_MAX_HEIGHT) and i < MAX_ITERATION: + i += 1 + limited_string = sanitized_str[:max_carac_amount] # .replace("\n", "").replace("\r", "") + w, h = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style).wrap(FRAME_MAX_WIDTH, FRAME_MAX_HEIGHT) + max_carac_amount = int(max_carac_amount / 2) + + if w <= FRAME_MAX_WIDTH and h <= FRAME_MAX_HEIGHT: + answer_paragraph = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style) + else: + # We may still end with a not short enough string + answer_paragraph = Paragraph(STR_TOO_LONG_WARNING, curr_style) + + return answer_paragraph + ######################################################################## # Specific attribute formater @@ -368,6 +436,7 @@ def get_date_value(misp_event, item, col2_style): return Paragraph(safe_string(getattr(misp_event, item[1])), col2_style) return Paragraph(item[2], col2_style) + def get_owner_value(misp_event, item, col2_style): ''' Returns a flowable paragraph to add to the pdf given the misp_event owner @@ -382,6 +451,7 @@ def get_owner_value(misp_event, item, col2_style): return Paragraph(safe_string(getattr(misp_event, item[1])), col2_style) return Paragraph(item[2], col2_style) + def get_threat_value(misp_event, item, col2_style): ''' Returns a flowable paragraph to add to the pdf given the misp_event threat @@ -396,6 +466,7 @@ def get_threat_value(misp_event, item, col2_style): return Paragraph(threat_map[safe_string(getattr(misp_event, item[1]))], col2_style) return Paragraph(item[2], col2_style) + def get_analysis_value(misp_event, item, col2_style): ''' Returns a flowable paragraph to add to the pdf given the misp_event analysis @@ -410,6 +481,7 @@ def get_analysis_value(misp_event, item, col2_style): return Paragraph(analysis_map[safe_string(getattr(misp_event, item[1]))], col2_style) return Paragraph(item[2], col2_style) + def get_timestamp_value(misp_event, item, col2_style): ''' Returns a flowable paragraph to add to the pdf given the misp_event timestamp @@ -471,6 +543,22 @@ def get_tag_value(misp_event, item, col2_style): return Paragraph(item[2], col2_style) +def get_galaxy_value(misp_event, item, col2_style): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event galaxies + :param misp_event: A misp event with or without "galaxies" attributes + :param item: a list of name, in order : + ["Name to be print in the pdf", "json property access name", + " Name to be display if no values found in the misp_event"] + :param col2_style: style to be applied on the returned paragraph + :return: a Paragraph to add in the pdf, regarding the values of "galaxies" + ''' + if is_safe_attribute_table(misp_event, item[1]): + table_event_tags = create_flowable_table_from_galaxies(misp_event) + return table_event_tags + return Paragraph(item[2], col2_style) + + def get_published_value(misp_event, item, col2_style): ''' Returns a flowable paragraph to add to the pdf given the misp_event published/published_time @@ -510,23 +598,13 @@ def get_published_value(misp_event, item, col2_style): return answer - -def is_safe_attribute(curr_object, attribute_name): - return hasattr(curr_object, attribute_name) and getattr(curr_object, attribute_name) is not None and getattr( - curr_object, attribute_name) != "" - - -def is_safe_attribute_table(curr_object, attribute_name): - return hasattr(curr_object, attribute_name) and getattr(curr_object, attribute_name) is not None and getattr( - curr_object, attribute_name) != [] - - -def create_flowable_table_from_one_attribute(misp_attribute): +def create_flowable_table_from_one_attribute(misp_attribute, config=None): ''' Returns a table (flowalbe) representing the attribute :param misp_attribute: A misp attribute :return: a table representing this misp's attribute's attributes, to add to the pdf as a flowable ''' + data = [] col1_style, col2_style = get_table_styles() @@ -570,16 +648,22 @@ def create_flowable_table_from_one_attribute(misp_attribute): if is_safe_attribute_table(misp_attribute, item[1]): data.append([Paragraph(item[0], col1_style), get_tag_value(misp_attribute, item, col2_style)]) - # Tags + # Sighting item = ["Sighting", 'Sighting', "None"] if is_safe_attribute_table(misp_attribute, item[1]): data.append([Paragraph(item[0], col1_style), create_flowable_paragraph_from_sightings(misp_attribute, item, col2_style)]) + if config is not None and moduleconfig[3] in config: + # Galaxies + item = ["Galaxies", 'Galaxy', "None"] + if is_safe_attribute_table(misp_attribute, item[1]): + data.append([Paragraph(item[0], col1_style), get_galaxy_value(misp_attribute, item, col2_style)]) + return create_flowable_table_from_data(data) -def create_flowable_table_from_one_object(misp_object): +def create_flowable_table_from_one_object(misp_object, config=None): ''' Returns a table (flowable) representing the object :param misp_attribute: A misp object @@ -616,13 +700,145 @@ def create_flowable_table_from_one_object(misp_object): # Handle all the attributes if is_safe_attribute(misp_object, "Attribute"): - data += create_flowable_table_from_attributes(misp_object) + data += create_flowable_table_from_attributes(misp_object, config) # Add a page break at the end of an object data.append(PageBreak()) return data +def create_flowable_table_from_one_galaxy(misp_galaxy): + ''' + Returns a table (flowable) representing the galaxy + :param misp_attribute: A misp galaxy + :return: a table representing this misp's galaxy's attributes, to add to the pdf as a flowable + ''' + data = [] + col1_style, col2_style = get_table_styles() + + # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, + # to_display_if_not_present) are store in the following list + list_attr_automated = [["Name", 'name', "None"], + ["Type", 'type', "None"], + ["Description", 'description', "None"], + ["NameSpace", 'namespace', "None"]] + + # Automated adding of standard (python) attributes of the misp object + for item in list_attr_automated: + if is_safe_dict_attribute(misp_galaxy, item[1]): + # The attribute exists, we fetch it and create the row + data.append([Paragraph(item[0], col1_style), + get_unoverflowable_paragraph(misp_galaxy[item[1]], col2_style)]) + + # Clusters + item = ["Clusters", 'GalaxyCluster', "None"] + data.append([Paragraph(item[0], col1_style), create_flowable_table_from_one_galaxy_cluster(misp_galaxy, item, col2_style)]) + + tmp_table = Table(data, ["25%","75%"]) + # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : + # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) + return [tmp_table] + + +def create_flowable_table_from_one_galaxy_cluster(misp_galaxy): + ''' + Returns a table (flowable) representing the galaxy + :param misp_attribute: A misp galaxy + :return: a table representing this misp's galaxy's attributes, to add to the pdf as a flowable + ''' + data = [] + col1_style, col2_style = get_table_styles() + + # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, + # to_display_if_not_present) are store in the following list + list_attr_automated = [["Name", 'name', "None"], + ["Type", 'type', "None"], + ["Description", 'description', "None"], + ["NameSpace", 'namespace', "None"]] + + # Automated adding of standard (python) attributes of the misp object + for item in list_attr_automated: + if is_safe_dict_attribute(misp_galaxy, item[1]): + # The attribute exists, we fetch it and create the row + data.append([Paragraph(item[0], col1_style), + get_unoverflowable_paragraph(misp_galaxy[item[1]], col2_style)]) + + # Clusters + item = ["Object date", 'timestamp', "None"] + data.append([Paragraph(item[0], col1_style), get_timestamp_value(misp_object, item, col2_style)]) + + + tmp_table = Table(data, ["25%","75%"]) + # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : + # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) + return [tmp_table] + +def get_image_value(misp_attribute, item, col2_style): + ''' + Returns a flowable image to add to the pdf given the misp attribute type and data + :param misp_attribute: A misp attribute with type="attachement" and data + :param item: a list of name, in order : + ["Name to be print in the pdf", "json property access name", + " Name to be display if no values found in the misp_event"] + :param col2_style: style to be applied on the returned paragraph + :return: a flowable image to add in the pdf, regarding the values of "data" + ''' + + try: + # Get the image + buf = getattr(misp_attribute, item[1]) + + # Create image within a bounded box (to allow pdf creation) + img = Image(buf, width=FRAME_PICTURE_MAX_WIDTH, height=FRAME_PICTURE_MAX_HEIGHT, kind='bound') + answer = img + except OSError: + logger.error( + "Trying to add an attachment during PDF export generation. Attachement joining failed. Attachement may not be an image.") + answer = get_unoverflowable_paragraph( + "" + NOT_A_PICTURE_MESSAGE + "", col2_style, False) + + return answer + + + + +######################################################################## +# General Event's Attributes formater tools + +def uuid_to_url(baseurl, uuid): + ''' + Return an url constructed from the MISP baseurl and the uuid of the event, to go to this event on this MISP + :param baseurl: the baseurl of the MISP instnce e.g. http://localhost:8080 or http://localhost:8080/ + :param uuid: the uuid of the event that we want to have a link to + :return: the complete URL to go to this event on this MISP instance + ''' + if baseurl[len(baseurl) - 1] != "/": + baseurl += "/" + return baseurl + "events/view/" + uuid + + +def create_flowable_table_from_data(data, col_w = COL_WIDTHS): + ''' + 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) + :return: a Table - with styles - to add to the pdf + ''' + # Create the table + curr_table = Table(data, col_w) + + # Aside notes : + # colWidths='*' does a 100% and share the space automatically + # 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) + lines_style = lines_style_generator(data) + general_style = general_style_generator() + + # Make the table nicer + curr_table.setStyle(TableStyle(general_style + alternate_colors_style + lines_style)) + + return curr_table def create_tags_table_from_data(data): ''' @@ -695,122 +911,6 @@ def get_good_or_bad_link(misp_attribute, item, col2_style): return answer -def get_image_value(misp_attribute, item, col2_style): - ''' - Returns a flowable image to add to the pdf given the misp attribute type and data - :param misp_attribute: A misp attribute with type="attachement" and data - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a flowable image to add in the pdf, regarding the values of "data" - ''' - - try: - # Get the image - buf = getattr(misp_attribute, item[1]) - - # Create image within a bounded box (to allow pdf creation) - img = Image(buf, width=FRAME_PICTURE_MAX_WIDTH, height=FRAME_PICTURE_MAX_HEIGHT, kind='bound') - answer = img - except OSError: - logger.error( - "Trying to add an attachment during PDF export generation. Attachement joining failed. Attachement may not be an image.") - answer = get_unoverflowable_paragraph( - "" + NOT_A_PICTURE_MESSAGE + "", col2_style, False) - - return answer - - -######################################################################## -# General attribut formater - -def safe_string(bad_str): - return escape(str(bad_str)) - - -def get_unoverflowable_paragraph(dirty_string, curr_style, do_escape_string=True): - ''' - Create a paragraph that can fit on a cell of one page. Mostly hardcoded values. - This method can be improved (get the exact size of the current frame, and limit the paragraph to this size.) - This might be worst look at KeepInFrame (which hasn't went well so far) - :param do_escape_string: Activate the escaping (may be useful to add inline HTML, e.g. hyperlinks) - :param dirty_string: String to transform - :param curr_style: Style to apply to the returned paragraph - :return: - ''' - if do_escape_string: - sanitized_str = str(escape(str(dirty_string))) - else: - sanitized_str = dirty_string - - # Get the space that the paragraph needs to be printed - w, h = Paragraph(sanitized_str, curr_style).wrap(FRAME_MAX_WIDTH, FRAME_MAX_HEIGHT) - - # If there is enough space, directly send back the sanitized paragraph - if w <= FRAME_MAX_WIDTH and h <= FRAME_MAX_HEIGHT: - answer_paragraph = Paragraph(sanitized_str, curr_style) - else: - # Otherwise, cut the content to fit the paragraph (Dichotomy) - max_carac_amount = int((FRAME_MAX_HEIGHT / (h * 1.0)) * len(sanitized_str)) - - i = 0 - MAX_ITERATION = 10 - limited_string = "" - while (w > FRAME_MAX_WIDTH or h > FRAME_MAX_HEIGHT) and i < MAX_ITERATION: - i += 1 - limited_string = sanitized_str[:max_carac_amount] # .replace("\n", "").replace("\r", "") - w, h = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style).wrap(FRAME_MAX_WIDTH, FRAME_MAX_HEIGHT) - max_carac_amount = int(max_carac_amount / 2) - - if w <= FRAME_MAX_WIDTH and h <= FRAME_MAX_HEIGHT: - answer_paragraph = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style) - else: - # We may still end with a not short enough string - answer_paragraph = Paragraph(STR_TOO_LONG_WARNING, curr_style) - - return answer_paragraph - - -######################################################################## -# General Event's Attributes formater tools - -def uuid_to_url(baseurl, uuid): - ''' - Return an url constructed from the MISP baseurl and the uuid of the event, to go to this event on this MISP - :param baseurl: the baseurl of the MISP instnce e.g. http://localhost:8080 or http://localhost:8080/ - :param uuid: the uuid of the event that we want to have a link to - :return: the complete URL to go to this event on this MISP instance - ''' - if baseurl[len(baseurl) - 1] != "/": - baseurl += "/" - return baseurl + "events/view/" + uuid - - -def create_flowable_table_from_data(data): - ''' - 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) - :return: a Table - with styles - to add to the pdf - ''' - # Create the table - curr_table = Table(data, COL_WIDTHS) - - # Aside notes : - # colWidths='*' does a 100% and share the space automatically - # 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) - lines_style = lines_style_generator(data) - general_style = general_style_generator() - - # Make the table nicer - curr_table.setStyle(TableStyle(general_style + alternate_colors_style + lines_style)) - - return curr_table - - ######################################################################## # General Event's Attributes formater @@ -977,14 +1077,13 @@ def create_flowable_description_from_event(misp_event, config=None): return Paragraph(text, description_style) -def create_flowable_table_from_attributes(misp_event): +def create_flowable_table_from_attributes(misp_event, config=None): ''' Returns a list of flowables representing the list of attributes of a misp event. The list is composed alternatively of headers and tables, to add to the pdf :param misp_event: A misp event :return: a table of flowables ''' - flowable_table = [] sample_style_sheet = getSampleStyleSheet() i = 0 @@ -994,7 +1093,7 @@ def create_flowable_table_from_attributes(misp_event): 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(Paragraph("Attribute #" + str(i), sample_style_sheet['Heading4'])) - flowable_table.append(create_flowable_table_from_one_attribute(item)) + flowable_table.append(create_flowable_table_from_one_attribute(item, config)) i += 1 else: # No attributes for this object @@ -1027,8 +1126,7 @@ def create_flowable_table_from_tags(misp_event): return answer_tags - -def create_flowable_table_from_objects(misp_event): +def create_flowable_table_from_objects(misp_event, config=None): ''' Returns a list of flowables representing the list of objects of a misp event. The list is composed of a serie of @@ -1047,7 +1145,7 @@ def create_flowable_table_from_objects(misp_event): 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(Paragraph("Object #" + str(i), sample_style_sheet['Heading3'])) - flowable_table += create_flowable_table_from_one_object(item) + flowable_table += create_flowable_table_from_one_object(item, config) i += 1 else: # No object found @@ -1072,11 +1170,11 @@ def create_flowable_paragraph_from_sightings(misp_attribute, item, col2_style): list_sighting = [0, 0, 0] if is_safe_attribute_table(misp_attribute, "Sighting"): # There is some tags for this object - for item in getattr(misp_attribute, "Sighting"): + for curr_item in getattr(misp_attribute, "Sighting"): # TODO : When Sightings will be object : if is_safe_attribute(item, "type"): - if "type" in item: + if "type" in curr_item: # Store the likes/dislikes depending on their types - list_sighting[int(item["type"])] += 1 + list_sighting[int(curr_item["type"])] += 1 i += 1 # Create the sighting text @@ -1093,6 +1191,30 @@ def create_flowable_paragraph_from_sightings(misp_attribute, item, col2_style): return answer_sighting +def create_flowable_table_from_galaxies(misp_event): + ''' + Returns a Table (flowable) to add to a pdf, representing the list of galaxies of an event or a misp event + :param misp_event: A misp event + :return: a table of flowables to add to the pdf + ''' + + flowable_table = [] + col1_style, col2_style = get_table_styles() + i = 0 + + if is_safe_attribute_table(misp_event, "Galaxy"): + # There is some galaxies for this object + for item in getattr(misp_event, "Galaxy"): + # flowable_table.append([get_unoverflowable_paragraph(item["name"],col2_style)]) + flowable_table.append(create_flowable_table_from_one_galaxy(item)) + i += 1 + answer_tags = create_flowable_table_from_data(flowable_table, ["99%"]) + else: + # No galaxies for this object + answer_tags = [Paragraph("No galaxies", col2_style)] + + return answer_tags + ######################################################################## # Handling static parts drawn on the upper layer @@ -1102,7 +1224,6 @@ def set_template(canvas, doc, misp_event, config=None): # TODO : add_header() # TODO : add_footer() - def add_metadata(canvas, doc, misp_event, config=None): ''' Allow to add metadata to the pdf. Would need deeper digging to change other metadata. @@ -1187,14 +1308,14 @@ def collect_parts(misp_event, config=None): flowables.append(PageBreak()) event_attributes_title = Paragraph("Attributes", sample_style_sheet['Heading2']) - table_direct_attributes = create_flowable_table_from_attributes(misp_event) + table_direct_attributes = create_flowable_table_from_attributes(misp_event, config) flowables.append(event_attributes_title) flowables += table_direct_attributes flowables.append(PageBreak()) event_objects_title = Paragraph("Objects", sample_style_sheet['Heading2']) - table_objects = create_flowable_table_from_objects(misp_event) + table_objects = create_flowable_table_from_objects(misp_event, config) flowables.append(event_objects_title) flowables += table_objects diff --git a/tests/reportlab_testfiles/galaxy_1.json b/tests/reportlab_testfiles/galaxy_1.json new file mode 100644 index 0000000..75efb5f --- /dev/null +++ b/tests/reportlab_testfiles/galaxy_1.json @@ -0,0 +1,1250 @@ +{ + "Event": { + "id": "30", + "orgc_id": "2", + "org_id": "1", + "date": "2018-04-09", + "threat_level_id": "3", + "info": "OSINT - PUBG Ransomware Decrypts Your Files If You Play PlayerUnknown's Battlegrounds", + "published": false, + "uuid": "5acc88e9-265c-4f22-9d2b-b702950d210f", + "attribute_count": "10", + "analysis": "2", + "timestamp": "1551342138", + "distribution": "3", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "1550506225", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "Org": { + "id": "1", + "name": "ORGNAME", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Orgc": { + "id": "2", + "name": "CIRCL", + "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f" + }, + "Attribute": [ + { + "id": "5291", + "type": "link", + "category": "External analysis", + "to_ids": false, + "uuid": "5acc8902-ab3c-4dfc-b0bf-32b6950d210f", + "event_id": "30", + "distribution": "5", + "timestamp": "1523391188", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "value": "https:\/\/www.bleepingcomputer.com\/news\/security\/pubg-ransomware-decrypts-your-files-if-you-play-playerunknowns-battlegrounds\/", + "Galaxy": [], + "ShadowAttribute": [], + "Tag": [ + { + "id": "9", + "name": "osint:source-type=\"blog-post\"", + "colour": "#00223b", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + } + ] + }, + { + "id": "5292", + "type": "comment", + "category": "External analysis", + "to_ids": false, + "uuid": "5acc9143-c550-4cac-9c62-40f9950d210f", + "event_id": "30", + "distribution": "5", + "timestamp": "1523391188", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "value": "In what could only be a joke, a new ransomware has been discovered called \"PUBG Ransomware\" that will decrypt your files if you play the game called PlayerUnknown's Battlegrounds.\r\n\r\nDiscovered by MalwareHunterTeam, when the PUBG Ransomware is launched it will encrypt a user's files and folders on the user's desktop and append the .PUBG extension to them. When it has finished encrypting the files, it will display a screen giving you two methods that you can use to decrypt the encrypted files.", + "Galaxy": [], + "ShadowAttribute": [], + "Tag": [ + { + "id": "9", + "name": "osint:source-type=\"blog-post\"", + "colour": "#00223b", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + } + ] + }, + { + "id": "5293", + "type": "sha256", + "category": "Payload delivery", + "to_ids": true, + "uuid": "5acc9181-5c70-4a02-b2f0-4dae950d210f", + "event_id": "30", + "distribution": "5", + "timestamp": "1523356033", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "value": "3208efe96d14f5a6a2840daecbead6b0f4d73c5a05192a1a8eef8b50bbfb4bc1", + "Galaxy": [], + "ShadowAttribute": [] + }, + { + "id": "5294", + "type": "attachment", + "category": "Artifacts dropped", + "to_ids": false, + "uuid": "5acc91b2-bd54-4e44-8aee-35e7950d210f", + "event_id": "30", + "distribution": "5", + "timestamp": "1523391188", + "comment": "ransomnote screen", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "value": "pubg-ransomware.jpg", + "Galaxy": [], + "data": "\/9j\/4AAQSkZJRgABAQAAAQABAAD\/2wBDABALCwsMCxAMDBAXDw0PFxsUEBAUGx8XFxcXFx8eFxoaGhoXHh4jJSclIx4vLzMzLy9AQEBAQEBAQEBAQEBAQED\/2wBDAREPDxETERUSEhUUERQRFBoUFhYUGiYaGhwaGiYwIx4eHh4jMCsuJycnLis1NTAwNTVAQD9AQEBAQEBAQEBAQED\/wgARCAIHBJ8DASIAAhEBAxEB\/8QAGgAAAgMBAQAAAAAAAAAAAAAAAAMBAgQFBv\/EABkBAQEBAQEBAAAAAAAAAAAAAAABAgMEBf\/aAAwDAQACEAMQAAABa21t81jC1YwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBYwFjAWMBZMZ0FZllTJ1hlogsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCxULFQsVCmXWrHWc7yWrid87cjr8m4XJOskxJBIsSASSFq2gklYm0lZtK1sBaCUit4spDCqWvKUGQLLQQSFCSoqyoqraXNZArExYTEkBJBIRIAExEgAFEhAErEgBIQSEEwkElQSJAAAVBIQTAAABEEhASoEgEgBAApIEVYRSbyqxlRZaE6O\/D0OXbLpwTnW8xoOnTBU6i8TB1qoNxzWm0xIOjbk6R7cjR08vqgMBYwFjAWMBYwFjATDMw3Dvwaxfk9blVSSdZgkIJhCJKJiQtWZbWpJdqOlKmd2fOkJ6djk6cvfs4kQal2Ttzec5PYs5tW2jn17Pn9R969s55Ylyr7nFsrC7awOR6TOuFGsMSO\/mOSB0wABIAEwBKwWFrJKQSJEgAAAABQAQSEEwgEkRIQSEEhESEEhBIRIKTEwALIABMTMysTMSxW1UpFos6PQ5\/Q5dmGOudbl5Q13whuMUmwwhuMIapySbTHU3GENxhDcYpNgAAAAAAAC13oRj059YvyuryqpMTvMwAEkkRaKAkgkAJDp8zpyzzu1mzvD38pHK7\/AJ\/0FnAIjc17ufvxeb2OJ3Tz8dq9l\/PdvjHXy9Dkypid2+aaa0TWEvGsU9N5v0mN+ajsFmHqW52dYuzy9G8768jsY1xdy9W8sZl52ddHm+i4KdJzePnfR5GhO8P6DeJm9bmnUrj9PndZK05HdlSzm6Kx9BLLMGmdphzdfCaMWzLHQOPaXXzvQ+f3nsTo4WNO1ZuhYL5nTl5FelzumIJLIJACSJmZYm0kWLSxW9SlbVKklnQ34N\/HqnTzJmumc9B16cyp2F85xtSQWtlDecyh1kc9xsbgWnTXzWq7XzKHWXgg6N+PJ0l58x1rc5B1DOo3ry7hePfg1i\/J6\/J1KEm8AEskSSEqBJBMlZkK9Ln9GVmLTzZXprOsz6HzvbzrjV6OKx2+EZuHucLvHnTTfeOjzutxcb6vPOmcJ8G+exjFY6cuIjryn0fm\/Sc98CyDpnv8h+nnvm9jBrROrkdeXm6c2rUz4t2LWfQ+d9Fw8b0bJ5Z2eVvyR0vP97nVk7\/O1HN6XN6acXucXtnG2GpcqN2A6Utw51pjPuOS3F2OnOmG2fnvu+f9BwNTv+f9BwI3beX1FQYN6ZsW3HqQSakEiRJYixKzMTEzUltWIqK2qABv3Yd3LadPMma6a8aDqHMqdhfOcbUkE3yhvOZQ62fA42NwLTpq5zVdr5lDrLwQdE5UnRpnzHajlXNpk0Doz6CMO7FrN+T12nBPSlnmo9MHmj0pZ5qfSEvmz0gecPRh509EL57R2Q42b0RHnT0RZ51neDBfYZ1zef6Is890OiHOOiHEzekLPN6u0GE3GbyI7BqecPRlnnOl0SPOHoyvP7ukS+f6O8MS+iS8NvXLniJ9CVz+d6EjmaNZLz8HfLOLu2C4sPbDiaOmRwOnsJUK2EvKy98rl5+4JxdHSNTgP7Bc409Ixvn4O+amDB3g4GvqBkXvM3gR6A1PPnoA4B3w4M90OHPbDiz2Q4p2heHHdDhR3g4B3xOfutXOk6eZMvTXjQdQ5lTsL5zjakgm+UN5zKHWz4HGxuBadNXOartfModZeCDonKk6NM+Y7Ucq5rtk0DTPoIx7MlzbDqxUwpNWKxZaKVuWiopwiEeIKfOcTQZxdM4zn22GadY0zlmXQJqPM0Z6azPOuTzPNOjPMrzPNy+M8mgy2XQZy40CIrQZyNBnk0GWy6DPNjxMWaDIzO3iDWHznJdBnkeIAywrj3vbPOWnVztNaDNFmquakuimWJdWziv3z7EZbbzScbMa6Zmt0jxC41mRo4SXDhIOEiuFA0TA8QQ4SK4TA+USNFAwUR0GobKhcno4RS9xLLTC5mlSzPNjJpMoTYpNqk1JIL1GKm0UkmoW+IrejFUXrYTCzTtz6PN3jJrwLbDuwklSrxUsIBCLTSy82UGSJHEJG1sTVtOPupMzz9C5Jz1dRi+3hpatuXrqXm4GoZ18y63rz9FZmM9a2i0pRhrFGxXfJyHK356ljh7qSXarEzclyu+Dksr08yyZ4fQrcnXNNicdaMqwTexcIU1eek53335VXvPP0wMqSrTk6ea1ks5+oi99YuvSmopdWdPW2dZXaSpep3f58SHTzhJEEiwBBFgqWhYJCIsRFossAEBMbNOfXjeLTjmzXOZEdBeStdBeZw5GiBc0B5koblZXK1iFprpkbFdeSlblog0s50mtcZjpGG8OWTTnZ9GbHP6HP1m+DelcsbSsVtYmQ1zWQ1zGQ1hltpFzToDMaRMkbSsUbRMRtizFOwXGbAxmwTGbBcZsDGbAxxtJcVtc1ijcJiNomI2lYjaGI2iY42hjNhWQ2BjNgZDWGQ1hkNZHM5fo8PPpybdaZeZXsKzvmT0ROcvqlnN3adU1lvqnpyyL3pXnZukc+traZ7cMhrLnIawyGsMhrFyGsjIawyGslyGsMhrDIawymolymsMk6gtqU3Gk6eZK9NeNB1DmVOwvnONqSCb5Q3nModbPgcbG4Fp01c5qu18yh1l4IOicqTo0z5jtRyrmuM8mhmfQRz+hz9Yu1XPXtnDk7ZxQ7RxZOyccOwceTsHIDrnJk6py5OmcyTpHNDpHMg6hy4s6pyoOscmTqnKk6hy4OqcojqnKDqnLDqHLK6hyw6hyw6hyw6hzA6ZzIOocuLOqcoOqcoOqcsOocsOocsOocsOocsOicm2b1a8LnZ166fIszv1dvLbq7teazWN0c\/np3jyxL6w47d56ZzCzpnMJemc0Okc0Okc2DpnNDpHNI6RzA6ZzA6ZzRekc0Okc2DpnMDqmXVmp08yV6a8aDqHMqdhfOcbUkE3yhvOZQ62fA42NwLTpq5zVdr5lDrLwQdE5UnRpnzHajlXNcZ5NDM+gjn9Dn6xfn9DnhNZqQkJJCSywWIrNpKTeSk3CszJUsJUvIuGRYurIKTYqs2IqWCpIVJCCQCSqlgrMhBMlSwVLBQtFlS0EEhBIQSESERN0yznTWbvqq1MaOhgitdC8dKdbJdcejR1dYqnZCc6r8Q60v3M0vXc0JmqySVJiCJCAAAAJiAAAUAIJgIkNWvLqzpOnmTL0140HUOZU7C+c42pIJvlDecyh1s+BxsbgWnTVzmq7XzKHWXgg6JypOjTPmO1HKua5y6SbpcRz+hz9Yvz+hgIJmokCZiVmaylprK2tW8TaJlAkgkSJAJgJgAiSoLBWZmKkzS6OqULgsuJUYC5uFC4ULQRIBEwREhBYKEhWbwUGQU0w6WefdOdDd11wx0JswK6c2ef27uLnW1K+0kXCgmIM2mLOO62OXswlliK6c9lZIqYmUiCSo6Yzy2VTLoEyypE3kVDai5uFSwP0JdKnTzJXprxoOocyp2F85xtSQTfKG85lDrZ8DjY3AtOmrnNV2vmUOsvBB0TlSdGmfMdqOVc1zl0k3S4jn9Dn6xfDuxLEyWQWFixYqWCJJC0TE2rKWtVi1mZlrF6kAElZ1gKRcsmZx0qXgpMrsYVlZgIAgkiQAIi0FItBATEEzZBIEVlArarES0TMSNVNVxasO+XfKJp0Z7MukQr+L1OQO7HB7k0RaArahcVUpxuryJdurFsR6xSOtSc2K2AfEbxciMWZpVXCrkqYvQCM0gIoEblipWp+fQJ08yV6a8aDqHMqdhfOcbUkE3yhvOZQ62fA42NwLTpq5zVdr5lDrLwQdE5UnRpnzHajlXNc5dJN0uI5\/Q5+sXya86wXCpcKzMrBNipcipaCKNrvlW0xvlJNS1LhR1o5d8t7X68F1tKRJVZtANS9OOsRMb4uUymO1Yhm+SrTFyF6zUWYnNCl98yGGdLG1zYiLWLbW2Oiy075zSy9R0tjj6Mw+nXljai6PtDdZreH527m9DNna8ezH05V6HO3Sze60q2hLSJKrnbnRjluUU5RVyW5VJMbtWbXL2LZ5vUsYnIvS8tpg6SYiIsRNVXdNsRS66dOfRvKdPMmumvGg6hzKnYXznG1JBN8obzmUOtnwONjcC06auc1Xa+ZQ6y8EHROVJ0aZ8x2o5VzXGe45mfQRz+hz9Yuh+eVhErNq2CSSCwsEzFZsFZtJS6VdOGyVRnbqqTrGusxjrFEq3y12yWmtVVg2ct9Y0xC8dWiLXDIpCsFiXiklysy2nPrwXSIRpmfmzEpxtgu6zF0TTrJt05shat89gsz1nKU7c8biM66bcbDSZ17x0YMed6eP0MqYejjvN9uE1Z0qUumrTNwrLqXNxpz6LjRKX465r1rebYts49MZqVz60amyaFwtSVuzaTCxwu0EWIpcYiY1M6xWituuU6eZOnTXjQdQ5lTsL5zjakgm+UN5zKHWz4HGxuBadNXOartfModZeCDonKk6NM+Y7Ucq5rjPcczPoI5\/Q5+sXz6M63mJlmS0FosqqMwGo5UHXjkEdc48HXryo6c+pPKLjq15cnUOWZ6dVOAuNsY4Ohq4l17BzdNz0FK056LjTG+abNJUOvMuVjowzRe0xV1V8PRWLL3mrKkrkXZjoiZjeJpSuOrZRbry0rjVcom+LS8LR25PjKY10GZuhSrMLH5XGdZ1asmsYqJ0Y69OXX3yyxpUq4qpAhGdXbbHneqF7lQzWXnneU5a0UVPn73qLmii66rRU2tmpkWZCVisQ5uLQy1mW9zsvn0enKdPMnc6a8aDqHMqdhfOcbUkE3yhvOZQ62fA42NwLTpq5zVdr5lDrLwQdE5UnRpnzHajlXNcZ7jmZ9BHP6HP1i+fRnmrzBm3nLBtnFJtyTmMtbUJlu85Yyi1LyLi8ETaBC2qGWmxC2qSItQkrepuqDbo5knbZwNm+fVfxLydJKZ6cNVERx66aIiabSTeKQHn9d6h6fJSxOdUi1pul4Zvld+a3PvZdD0eVFdFc6VGklRom+sywXNWZRoYOjg5ejh6EVX0bufsIQ5QoZVM9Yvx66LIvLd+FnXnuF2QGszrJDK8uhBXFWp6baBSmCg1Xx3hkRda2vMTZNTo68W30+dOnmTvPTXjQdQ5lTsL5zjakgm+UN5zKHWz4HGxuBadNXOartfModZeCDonKk6NM+Y7Ucq5rMzhl82kjn9Dn6xdLuTNdKvOtnUqWWMikF4XAytSL2XKsmklppMWiAkiSIvJUkKqYmpraCtgQrYJDTc5JcqarFomiY1XKYrGNtlVltOjGy5+TTz7Xit5sq6vTirZl0402yb3lYzV7Y0ZqxS2zeSL0Zz7bLXv05ojQb5xaJzsxbcNnCGszu\/V5nWEK0rpcMXm5WLnh3bNCWGrp247Z5srtqOzSYtBWYyrNDOpyacrUFiqQxdDE2NAi8QxYvT387o9\/OnTzJ3jprxoOocyp2F85xtSQTfKG85lDrZ8DjY3AtOmrnNV2vmUOsvBB0TlSdGmfMdqOVc1mZwy+bSRz+hz9YvxO3xJpUwZszWaCAvZZErmAAWbUBkosMlcjIgGkSSRBVdlklIGQAAFtORu+WyuW\/Tg6+SS78FppuaY5ejoVjJ18222K66s9XqzOxHD0apz2NMUvrE3tW5ErVrNYg8\/s2zmjv5tlslLnoM5hvl1Y5rTfCbTTasucI6nO4+mezyevUVZWLUhS41XXx7EWMdWVq1nOzVkudL8Guy5FMm3zUXSizDI0TnckRaUtWxUzXUvfPdL0spex0+Z0+3nTp5k6z0140HUOZU7C+c42pIJvlDecyh1s+BxsbgWnTVzmq7XzKHWXgg6JypOjTPmO1HKuazM4ZfNpI5\/Q5+sX4fc4U0uambYrBYgLRVthVzenDAb1zWLTm6i4l7q6xjNRneVmiUI1VszV1RLhrtqYqbs2ekRtnXPDOwXJfTfXPnmtXPql6NVY46GYS5WuXFJsm8TW31zRTaCLujHVVrzmxat7HLWrXOIs7eaKfSM93RNMupusKq0ms9ehFnOZrBEzOsTnfhzra7M3fKFyrHUtD98+bofTPRt6X5dVupZL3XTKlrL3nStc0UuY2qbZefo0ofn3yvdbeHotSKLZcr3mZpNjq1fnXS6XL6nbzp08ydZ6a8aDqHMqdhfOcbUkE3yhvOZQ62fA42NwLTpq5zVdr5lDrLwQdE5UnRpnzHajlXNdslxzM2kjn9Dn6xfg97gzSgJSJsUhpCy1K35VGuWtK9U2OlIxNK6xrSuDTbHol2yqCaKUQ\/NWa1ZoDWpM2a6ZS52Xza5bY9eQW\/HM1rrmLk34ayzryRNbDKaxqvzw3xhJrSzE\/O9Vstt4bReO52wi+N6IzLud84WGqchZqrlm52VwSvSOcHRy5ya1O5pc9KuEN0Y22WrWmdOvlXZ0X8lsdLLmqup2Ccb2WxtrbVFN8ntxacbdz+rXh3zZNd9Yx6Lo5+piW6GcK9CN4rFhXEznfS6nM6fbzp08ydZ6a8aDqHMqdhfOcbUkE3yhvOZQ62fA42NwLTpq5zVdr5lDrLwQdE5UnRpnzHajlXNdslxzM2kjn9Dn6xfgd\/gTSQJSYIsQBWYCIC2hfTMB0c5irKlZdcjrVsRVyRNrVGRFxNqWLUmAmljdrU0jHqzGKHKWL1hG0owiCwyG1XIExJW01N6lkibWacunHnT7JeLhlSGocly9tc0yympSlhYGXjNTbQyDKFa3qMiBaFxJpNFu7O+UrNZq9ZZncPiy4x6tY01UMdW+K\/n77jA6NHNYnVsxczc57J1i82TrDr5GJ3ejyur05p08ydTprxoOocyp2F85xtSQTfKG85lDrZ8DjY3AtOmrnNV2vmUOsvBB0TlSdGmfMdqOVc12yXHMzaSOf0OfrF\/P8AoODNZ5tbNWNkUNJUy0FWtUu7NBsplkvEVqzM8JojODq1Bi7UW0zaK2Q4XXXUz3rWuoZSXUhdBiaTUxOpMk6arnZoqXXLznDERaLRZYuqqjKk3rXOtE55LSqQ1533Lytdc71om15kJXLJKyVLiqo2qwtECzW0RExAyL1WalZGTWc9NRmtOk0kZevZk1kcmuejrZ2Y2+qoVopdmiFwkotTfO1k31z73Y43ZROnmTXTXjQdQ5lTsL5zjakgm+UN5zKHWz4HGxuBadNXOartfModZeCDonKk6NM+Y7Ucq5rjPY02z6COf0OfrF+F3fPzVBc5t5pErBYMikVeVA2tIGwuQiYSskE2rIxcwXLCzWYijVQa652BR1LLi5zq0VoSLbqXl1VS1kCLWaUS5BSIrcstFgVaCLzEtZgCQJiYL6M77m8QXIFS4iq3XFhU3pFYmqyRITBUzWyEMiVZaC67QpdcysrS0rGIJrZmpYkpFaGZr51eq6IxYaxMBZBeEoEV3u5w+4J08yTprxoOocyp2F85xtSQTfKG85lDrZ8DjY3AtOmrnNV2vmUOsvBB0TlSdGmfMdqOVc1xnsabZ9BHP6HP1i\/nfRRNeXPWEvlI9YHk59WHlJ9VB5U9VJ5OPWweUj1geSPXB5I9aHkj1oeSPWweVn1JHlY9WHkz1geTt6oPNK9WHk49aHkr+qk8pb1IeWt6eDzEeoF8zHpw8xT1Qnla+sK8vT1YeUn1QeVPVB5U9UR5U9UHl49UV5W3qJTzM+lhPOnog87X0oeUT7Cq+Rj14eQPXi+QPXieRPXSeRZ6qTzU+lDyqfXB5SPVi+Uj1oeRn1pL5M9YHkb+rDyc+rhPKT6sPJnrA8nHrQ8merDy0eqk8lX1wc\/uKbSdPMk6a8aDqHMqdhfOcbUkE3yhvOZQ62fA42NwLTpq5zVdr5lDrLwQdE5UnRpnzHajlXNcZ7Gm2fQRz+hz9Yvi2+fl6Zxia7MccOwccOxPGk7Bxw7Bxw7E8cOwcmx1Dlh1Dlh1DlB1TlB1TlCdU5YdQ5YvVOUHVOUHUOUHVOUHVjlB1TlQdY5IdY5IdY5IdY5IdY5IdY5IdY5IdaOUo7ZybJ145lLOpbkXOrHMg6cctJ2Y4tV9Xo5PQjBXjVXtHCE7pww7hww7s8Kx2jiQd04ovaOKHbOIHbOIHbOIHbOIHbOKHaOKHZOMHZOKHaOKHaOKHqdvF7QnTzJOmvGg6hzKnYXznG1JBN8obzmUOtnwONjcC06auc1Xa+ZQ6y8EHROVJ0aZ8x2o5VzXGcNV8+gjn9Dn6xfgd\/gTTn12RjXqUsWo4KbKHK0qaXvDoRg6\/Jrqdni9oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADx3sfHnr7VsUuqo6c8jxLg8Z7PxqIi1a7vQwb5fKwB1kzmsdn6dI5\/QqDqc3snOV16mCuuy5p05oy6c+6suXp8sCYByXJ0+b3FLyG77HDNVTOOSAABJ2u1xe0J08yTprxoOocyp2F85xtSQTfKG85lDrZ8DjY3AtOmrnNV2vmUOsvBB0TlSdGmfMdqOVc1xnDVfPoI5\/Q5+sX4Hf5U1gNpLiNsGM2SYo3BhNwYZ2BjjaCtSoGigaKBooGipGCwYKBoqRgsGCgaLgbCwaLBgsGQsGSoGigaKBooGigYLBnO2hptjlNcZqmucYbYywauPtgwRvDfvz7F8gbpOfXpUTBO6xhncGE2yc\/baTDO4XCbgw31hijcGE3BhjeGE3BhNwYDoBgN4YDeGA3hs7XM6YnTzJOmvGg6hzKnYXznG1JBN8obzmUOtnwONjcC06auc1Xa+ZQ6y8EHROVJ0aZ8x2o5VzXGcNV8+gjn9Dn6xfRnyS9o4EzXeOOg75yA65xbnXOOHYOGw7BzEnZPOLPTnkaHsTxUHtjxMntTxQe1PEh7Y8UHtTxQe1PFB7U8TJ7U8VB7Y8VJ7Q8ZB7Q8VJ7Q8SHtjxMntTxIe2PEh7Y8QHtzw8p7c8QHtzw4vuDw8p7c8VU9ueHse2PFQe2PEB7c8Mw9qeLg9qeLhfani6p7Y8QHtzxAe3PEh7Y8Rc9oeMg9oeLlfZni5PZnjIPaHjA9meLD2h4sPaHiw9oeLD2h4sPaHiw9oeLD2kcbsidPMk6a8aDqHMqdhfOcbUkE3yhvOZQ62fA42NwLTpq5zVdr5lDrLwQdE5UnRpnzHajlXNZmeS1LiOf0OfrF3ofNOW1Od55pbPoIdVnRzrom+nh34bjUrXzWddKS0WrddVbr156xNfB0CxqVU6M3PXdXpMiejXLnnRv1nMtunjeWjtHSca\/VnncUborks7E+3nlXuK5aO1U41+jJzK9qpx69tByzdBkruucyvSucmOw04Megg8\/PpRPMX9JY867srOYnr2PPs7NTEt+9eJXtQcS3WUYa9Sxzl9TMZq9Bpyq9NJhjrMOLPdDjHXWc1PXDkr7EHCjuVOLHYWcuOvByp6knKjtLOSdOTlT11D+xzuiJ08yTprxoOocyp2F85xtSQTfKG85lDrZ8DjY3AtOmrnNV2vmUOsvBB0TlSdGmfMdqOVc1mZ5LUuI5\/Q5+sXejRK5dyaxX1E6Uq0YyapFymoWcWwTE9wuNmgWUtLhYw4VcNBQ0ypDDaFOBTQsXVxiqGgoaQuriqxc3JIOskgJgCYAmIkkrJJAQSFl2BFdIIlwLuSVreRUOkz2aFIYFBkFKOBNmQVq2CgyCsyFbwFqyBWQkAAAALRWSSskkBaKyTNJJggvEQi9OKq9BedJvMKzqrxNNSYYUvnuajBQ6WfIw1NyqTerFdWa8SjpLyVNxgk10Uk6kc6xoEtQami6Ofuw6wtgWACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkD\/xAAwEAACAQQCAQIGAgMBAAIDAAABAgADERITBBQQITEVICIyM0IjMAVAQVA0YCRDcP\/aAAgBAQABBQKnTULisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWYrMVmKzFZisxWOixfs\/wD4a3tj4tPZn9l+z\/8AhtT7bvKdTFfriXzqeyfjzSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxJsSbEmxIzU2H0T6J9EQLlV9qX4n+7++0tLf02lpaW\/+n8P3YAvkpmCTBJgkwSWpZYJLU83wUpraYJMEmCTBIopMMEl6WvBIVpqMEmCTBJgkwSYJMEmCTBJgkwSYJMEhRJgksBK320vxP93\/ANs4fv8AuoQRqd2RQKmJpUSlJahpvArYU8FqVDjVcO4CNhgjRV\/moKpUIccfWxHFY5nX9KWw\/sMZapcyt9tL8Tfd\/eqM80VZoqw0qg86ankAsTTdQPWaqk1VIaVSH0l5eKGedepOvUjKV+Xr1p1q061aGhWH\/wBA4fv+\/kqCcRl84UCKoUf6TS7TK8rfbS\/E33f38SVauudqU66uatPNfkofkr\/jT7ybDtReUpNWmKiyjS2N9FNW5Ri8sz6Ki1k1vf5O5O5KVZak5VMW\/wDe4nv+4ovmtBxBRe2l8TRfFqLmGi+YovmtBxBRe2l8TRfFqLmGi+YovmtBxBRe2l8TRfFqLmGi+YpOH\/qb3aLK320vxN939\/EnL8L7\/LQ\/JX\/Gn3v9kAJPsD6njrjT5LXbxSraxWqbfn419tf8SqWKcamstx7vx6bRlKtx6SOvWpXxok1uOtpT46gWoXahTaMpVqNDOa6KjVSYVqOuUgGqHj0phRWVKCMEoU0Gukwq09bJRoslVcX49JXHIpKgpLk70aKrRo7JqpKAtFpW44Ag49KClRpivjnT4yiY0bvxkaMpU\/6fE9\/33E\/LkMpkMybBatyagEZwq+TUAjOFXxl9Qr3HhWDRWDCZiLXpsFYNB7O+EV8i0xmNpW+2l+Fvu\/v4cq0tk6kp0FSVqoRfk4\/5eR+JPvNp\/wDjxdcrK7LKf46v5IvHyXqyrR1j5OpTnUpxUp0hyK2cpPrapyS666k4+Wvlfl4n2cmowikhj7U\/vYXDUKixa7oGY1GAxFRy7UXKvUXJKH5ajYoSSUN0ruWfimcucVrryh9VJcadVcqfFX6uU1lp8jWr1jWdVqhm+2D2di7cZb1KxYJrqTjl8eWv9dv6eJ7\/ALqEEandkUCpiaVEpSWoabwK2FILtdc6S1Lx1jNsVVtXoq4fUBSKnFm2Kq2r0VcPTXWim\/Hqo+br\/MqHNEULZc7EDHJFp2JT6K\/rLelAWaGVvtpfhb7v7+JORUdJ2K0NaqfI9j6Gcb8vJ\/FT\/JU+zxRYtT5AtVoG9LkLapBXqAUKru\/K+z5NlSLWqKUcVEr08HpU9jWp0lPLEpVNi8r8vE+zl\/fP+ROSRF5FMwqjh011W+2D3PtQ\/LX\/ABSl+Op+TiTlzjtapVTMVWxp0myp0kwnIN6lCiGFStTohefkT7Qe04nvUqCmO1TnapytWWov+nxff99xPkqCcRlkMpkMybBatyzrGcKvlnWM4VfF1LCvceFYNFYMMRlkt1r02HhaivEqB4fBlb7aX4W+7+\/iTl\/LQfKnW47FtVQyhR1zlv6U\/wAji6detF4tUxVCLWfOpx6uJqUxUVqFVYtGq0pUhTHK+z5uK31clb0+J93IRnUUahlKnrXlfl4n2cr75\/ylbN6SlTSqCUEZF5J+tGzWrQYGjRN6zYpQ\/LX\/ABYmU\/sNO701xlcAywBHqOUfTin0JsCbmj+PkUnyocR8j7Qe0oPi9RM1NGoDRo4jk4g\/6fF9\/wB1CCNTuyKBUxNKiUpLUNN4FbCkF2uudJal46xm2Kq2r0VcPqApFTizbFVbV6KuHprrRTfj1UfN1\/mVDmiKFsudiBjkgV9gT+bjjCU\/SUxYwyt9tL8Lfd\/fSq65Vq7PlR2QjlidqnG5ZhJJU2buTuTtx69R\/NPkOkHLSHlpO0+VWvsXz2\/Ktix5d4CVK8sQ8tJ2zKr7GpVtYq1Nhna8JySJ2qcblT3lOoyHsrDyljuXKNiz8jJczKdb6chN+JqV85lF5WK1K2bU6mDPyMllOq1OdpIeZO147XlOQyjspG5MPr\/qcX3\/AH3E+SoJxGWQymQzJsFq3LOsZwq+WdYzhV8XUsK9x4Vg0VgwxGWS3WvTYeFdHiVA\/kyv9tL8FH8f\/wBs\/wC\/uoQRqd2RQKmJpUSlJahpvArYUgu11zpLUvHWM2xVW1eirh9QFIqcWbYqravRVw9NdaKb8eqj5uv8yoc0RQtlzsQMckCvsCfzccYSn6Sl9sMr\/bS\/BsqCbqs3VZuqzdVm6rN9Wb6s31ZvqzfVm+rN9Wb6s31ZvqzfUm+pN9Sb6k3VJuqTdUm6pN1Sbqk3VJuqTdUm6pN1Sbqk31JvqzfVm+rN9Wb6s31ZvqzfVm+rN9Wb6s31ZvqzfUm6pN1Sbqk3VJuqTdUm6pN1Sbqk3VJuqTdUm6pN1Sbqk3VI\/KqCdyvO3yZ2+RO1yIterbsVZ2Ks7FSdirDy607deduvF5FQzbUm6pDyq0pcms021JtqTbUm2pNtSbak3VJuqTdUm6pN1Sbqk21JtqTbUm55uebqk3VJuqTdUm6pN1Sbqk3VJuqTdUm6pOO7MaxKzZUm+rN9WGo5O18ttW2+rNtW26rDUqzOoYKtUnfVm+rN9WZ1DBVqk76s31YalQjOrffVm+rDVqiGtVB2vlsqmF64m+rN9WM9WNUqicc+kMr\/AG0vxf6JNpfzcS4g9YxtMpl9XyE2lxLjx6eAb\/L6T0npLiekJ+bL18XEuJcS4npLiekMtLSwgHrkJ6fIRLGX9VYxWEuIwlH0ga8uPAe5V7n\/AF+L71\/Y3gb0J+m+T5MVyWXF2vgDZisBgFmv9DkWy+q4uBZr\/Q5FmORP5FIsD9JPoSb+tveXsbjG\/wBFT1jesp38GVvtpfi\/0W9k9\/8Ao+39WiC0qR435PdvXFPFSNP2X2\/40\/f3hvZfaP7\/APR9v\/DG9mjLaJ4qT9\/2B+lFg9v\/ANcH5PeN9sH2D7bGC+Jnr4\/5LeLeoMRbgR\/uBie3uHi\/kQev+vxvdgGayGaKU0UpqpiaadtVHLRSmujmaNIBRSJK0VjJRVdFKaKU0UoVorGSiq6KU0UprpZjURopTRSi06LRaVFhpp2xoxesw0UpopRBRafwYIVPgyt9tL8X9dpaWlpaWjeyS1jicbGzAwezxgYfvtZ\/qsARFNw8YGWOQBtY2aKfUDEm9hcKPZ\/exuAbWNjeEGxEN4ARFNw4nrliQwT6VyliJY4WMA\/kUFSwYi1p+q28\/wDFntP1Fr+kvLGYG1P0UD62+6IIuSx1MW+aZX\/1+P7\/ALqEEandkUCpiaVEpSWoabwK2FILtdc6S1Lx1jNsVVtXoq4fUBSKnFm2Kq2r0VcPTXWim\/Hqo+br\/MqHNEULZc7EDHJAr7An83HGEIbFPVoZW+2l+L\/xLCWHki\/+vWNhe89YLw38ZGZQsfFrxEFh6eLTECPB7j2\/26Hv++4nyVBOIyyGUyGZNgtW5Z1jOFXyzrGcKvi6lhXuPCsGisGGIyyW616bDwro83LiHz8GVvtpfiWg7jq1J1ak6tSdWpOrUnVqTrVJ1nnWedZ51nnXedd513nXedd513nXedZ51nnWedZ51nnWedZ51nnWedZ51nnWedZ51nnWedZ51nnWedZ51nnWedZ51nnWedZ51nnWedZ51nnWedZ51nnWedd513nXedd513nXedd513nXedd513j8Rmnw6rPh9aD\/AB9WNwapnw+tPh1efDq0+HV58OrReBVEHFcTrvNDzrvH4tUw8LkGDg1oOO867zrvOu867zrvOu867zrvOu867zrvOu867zrvOu867zrvOu867zrvOu80PNDzQ80PNDTQ0poUP7qEEandkUCpiaVEpSWoabwK2FILtdc6S1Lx1jNsVVtXoq4fUBSKnFm2Kq2r0VcPTXWim\/Hqo+br\/MqHNEULZc7EDHJAr7An83HGEIbFPVoZW+2l+Kk6hNizYs2LNizYs2LNizYs2LNizYs2LNizYszWZiZiZiZiZiZiZiZiZiZibFmwTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTMTdTm6nN1Ob6U30pvpzdTmxTLzMTMTYom+lN1ObFmYmYmYmYmYmYmYmYmYmYmYmYmYmYmYmYmYmYmYmYmYmYmYmYmYmYmYgNz++4nyVBOIyyGUyGZNgtW5Z1jOFXyzrGcKvi6lhXuPCsGisGGIyyW616bDwro83LiHz8GVvtpfiPv8A\/QtamBFErD1It5EQkQV7Q2aYGV8pg09bp7f7fH9\/3UII1O7IoFTE0qJSktQ03gVsKQXa650lqXjrGbYqravRVw+oCkVOLNsVVtXoq4emutFN+PVR83X+ZUOaIoWy52IGOSBX2BP5uOMIQ2KerQyt9tL8R9\/9W0t\/5RYCGsIKrmese8BjU7QeB+Ks049xEj0xBCogH+5x\/f8AfcT5KgnEZZDKZDMmwWrcs6xnCr5Z1jOFXxdSwr3HhWDRWDDEZZLda9Nh4V0eLVVlVw6wyt9tL8R9\/wC239Fvlt\/4gQxrx84FvFS3iwlVbMrB1t6pRjGUqFyEUealO89UggW8KTEj\/Z4\/v+6hBGp3ZFAqYmlRKUlqGm8CthSC7XXOktS8dYzbFVbV6KuH1AUipxZtiqtq9FXD011opvx6qPm6\/wAyoc0RQtlzsQMckCvsCfzccYSkCJSBFOGVvtpfiPv\/AO6iw+kYzWWgpTAzWZrjUCw1GmU1ypUURELlRYebSpSvPUSnUl4YR\/fb+i0o+\/77ifJUE4jLIZTIZk2C1blnWM4VfLOsZwq+LqWFe48KwaKwYYjLJbrXpsPCujxaqsquHWGVvtpfi\/7\/AODaWlpaWlpaWlv9C3lFhlWpFF4o+Z1DK4xNIBnVQvz1haKbHKD1jQ\/Na8wmEwmEwmEx\/qpe\/wC6hBGp3ZFAqYmlRKUlqGm8CthSC7XXOktS8dYzbFVbV6KuH1AUipxZtiqtq9FXD011opvx6qPm6\/zKhzRFC2XOxAxyQK+wJ\/NxxhKQIlIEU4ZW+2l+L+8f+bbwWh96I\/o5HoeN7+by8uJ6SoLxvuVoGJ8WmMsJaWgEt8t\/B\/qo+\/77ifJUE4jLIZTIZk2C1blnWM4VfLOsZwq+LqWFe48KwaKwYYjLJbrXpsPCujxaqsquHWGVvtpfi\/uBBl7f0ZCZAnMQvb5iwEBv\/qZDxf5SQJsWMVYWOVNgs2iZgzO8Uq3jck3JOQbvx2s9\/kIExWYxwwB91tAT4L2KteXl\/AhdRLy8vL\/JaW\/oo+\/7qEEandkUCpiaVEpSWoabwK2FILtdc6S1Lx1jNsVVtXoq4fUBSKnFm2Kq2r0VcPTXWim\/Hqo+br\/MqHNEULZc7EDHJAr7An83HGEpAiUgRThlb7aX4v7f+KcYbvMmMBe+ZuCxa7FgxvP2QC+TZVPuLG5Lgs5uCbxmIhYgXbwzWhyhJEyMLGXe7FoWIJLQn0JaHIQk2JImRhyhJub3jT6hLm1\/QXj\/AGhgBb6f2X3UtAbIhGFIJeZHYxzBAuMMlLYbmstRsAzmF7LlVm14ajEYgxfQN7k2L2uDaPksuZkZdhH9x59PF\/ltMZjCPND3\/fcT5KgnEZZDKZDMmwWrcs6xnCr5Z1jOFXxdSwr3HhWDRWDDEZZLda9Nh4V0eblxD5eDK320vxCWlvFv6it4ARCpipBTYFUZYEN\/DKTAJhMbllvAhuVe+HrCl5i0wJ8Mt5gYVvCphW8+qFbmEtf3L++F4y3gjLeBTfH1w8EXmEbIEwCMCQq4x4XtEN4FMFOCm0RMfGm0FIxji2YEo3ZRTxAR7a3tq9MGjCN7M8X2sScIR6w3bxa8CGEGDxb5MpfxeZS8yhMv4o+\/7qEEandkUCpiaVEpSWoabwK2FILtdc6S1Lx1jNsVVtXoq4fUBSKnFm2Kq2r0VcPTXWim\/Hqo+br\/ADKhzRFC2XOxAxyQK+wJ\/NxxhMmprTtaGVvtpfiHt\/d6S3yenyX+W8BH91vHpL+bj+i\/i8vHvHlF4vr4tLiCNBaV\/wAk458HzkIbxry94pMDGXvCIYvqMZi09fIPgjzeZTL5cbw05iZR9\/33E+SoJxGWQymQzJsFq3LOsZwq+WdYzhV8XUsK9x4Vg0VgwxGWS3WvTYeFdHm5cQ+Xgyt9tL8Q\/vq\/ar4gPcbBYsSiA2X2JsC95nY3aK8zEtlHguPDNaFjM\/VnAgqTP1zmfrmL5euVj\/8AslSL9qj1W9p9N7+oPi\/qH9TUgbKe7Lfx\/wBczXAmMDWm2GrFtmCJWvBdZVTJilhx3\/k2AQ1PXbM\/U++RjXmMC4kWgIvHlrRcgPBWWgl4W8XEJHm\/y38L7\/uoQRqd2RQKmJpUSlJahpvArYUgu11zpLUvHWM2xVW1eirh9QFIqcWbYqravRVw9NdaKb8eqj5uv8yoc0RQtlzsQMckCvsCfzccYTJqa07Whlb7aX4h85qATek3pN6zes7CzsrOysbkKw3AAVVx2DHcpXNbCuBDXDTcJmt8aYKsLq12VwI3rLet4wM9WbFr\/UpGRIByCqIAcrKJkHi3Ln7o4Yz1tF9JeWN\/++svDP8At7RT6gRWxJ9YYYQl2Kmf8AgDCANlKgJYr9VRZUKhaFxU9cvqyKm\/7EG5EY2h9YfQ5CFlmwTO8+ojGZOJeEy8vC0vL\/NeZQHxeZRDc\/vuJ8lQTiMshlMhmTYLVuWdYzhV8s6xnCr4upYV7jwrBorBhiMslutemw8K6PNy4h8vBlb7aX4h87KDHS0vLy5lzLy8vLmXMuZcy5lzLmXl56y8yl4KpEXkTsLAymW82+U439B4vMplLy8HnKF5eX8CARlJhyWZEwmXhlJhMlmSTYJl4PvX+2UvvB9IbTK0arMiZa8ExyDq4KUYlGnPpXwb+Ly\/gwy8vA0yHgARl8XgMzmUvKPv+6hBGp3ZFAqYmlRKUlqGm8CthSC7XXOktS8dYzbFVbV6KuH1AUipxZtiqtq9FXD011opvx6qPm6\/zKhzRFC2XOxAxyQK+wJ\/NxxhMmprTtaGVvtpfiH9FWW82lpaWlpaWlpaGXg8mD5LzMxaxEHJMHISGoCKdz4LLFJyJW9xLie4t6ePWAmFjAT4tLGAS0T0hPpeFyY3vixipDTlGWgS0+mN6RPYicj8YMVvWmfpv4N\/LHGLA9oWEFWZy5MNhNonvCPF4Yflyi1Jn5sZaDxx\/f8AfcT5KgnEZZDKZDMmwWrcs6xnCr5Z1jOFXxdSwr3HhWDRWDDEZZLda9Nh4V0eCqCofLwZW+2l+JfJPjIzKZSo0v4pWyfDE\/0N4Hyn57yjeOXUpyD4vbxkL39dmJ2ZC8vFggn\/AAXgvl\/0D6rTEmfUBP8As+u48Y2F1hwhJANsE+0zkA4WhlA\/TaGZS0tD6mWlpaZRLwp6qqw4w+LeCIRLH5rweby84vv+6hBGp3ZFAqYmlRKUlqGm8CthSC7XXOktS8dYzbFVbV6KuH1AUipxZtiqtq9FXD011opvx6qPm6\/zKhzRFC2XOxAxyQK+wJ\/NxxhFukpDwZW+2l+JfaH2ymRmRmRlz8gNpkf6bTHyfmPz3l5e8DHx7QsTLzJoCfF\/FjPWCIsxhsJsmcvePLzLwoiggWhS51zES3jkfaQZYyj7iHxe0vf5TFUTO02CbZcmD2+S0Ih8H5AZlLy\/jie\/77ifJUE4jLIZTIZk2C1blnWM4VfLOsZwq+LqWFe48KwaKwYYjLJbrXpsPCujwVQVD5eDK320vxPUcMKzTbL\/AC+vyCX\/ALD4HzmmBHTD5LGaQFKlfItHRVS8Wek9PFrzETH1U2mRl7zG8\/65xi2YlJj6AXlIXPz1nuLmZSlYvj6FJ6TEQr8l\/FyJsYwesVRLfLeZRm82lofmynD9\/wB1CCNTuyKBUxNKiUpLUNN4FbCkF2uudJal46xm2Kq2r0VcPqApFTizbFVbV6KuHprrRTfj1UfN1\/mVDmiKFsudiBjkgV9gT+bjjCLdJSHgyt9tL8VT7\/6D4v8AJf5r\/IfA828qLl1UCp7MAq1PSExxUyf1lZTF93JWVPx1ftJwh+lKTrACJeU\/ayCCAeo+7x7tVH1D0jeij7F9gfT0jND99T0Yo12vkuCtUQ5OtmtKIIcGWmI8ECP6eLmXl4fWWtAYDBL+L+CsI8ALbyfN5fzwff8AfcT5KgnEZZDKZDMmwWrcs6xnCr5Z1jOFXxdSwr3HhWDRWDDEZZLda9Nh4V0eCqCofLwZW+2l+Kp9\/wA9\/k9JcS8ymUvLiXHzH+nYYajGbmm55uaM7GGsTGZmi+9SoLF2MNV5vebngqrbMY2iuRPUwXgvAIYXsTVbyahM2NMjbYwJqMZtabjNwg5ImVIxdd8EJrUry1jTvmPa0us2rGqLGcecZaATEEFcSGiy0vMpnA89I+PgNL+bfNwZ+6hBGp3ZFAqYmlRKUlqGm8CthSC7XXOktS8dYzbFVbV6KuH1AUipxZtiqtq9FXD011opvx6qPm6\/zKhzRFC2XOxAxyQK+wJ\/NxxhFukpDwZW+2l+Kp9\/9ApuYVZfk1PbTUhFvF5eIC0xt8h\/oALRkK\/IaLAeWpsvya2x1tYQS\/i89YGmYhuYUaIhMFMxvCqWiJDTENKazLeQrGBXis5mVSFiSA6Q1akau1juMZXxFM2iBiAPTwLwXMIEKWivMxD4Pi5gUnxeKCZY+L\/J7gAicL3\/AH3E+SoJxGWQymQzJsFq3LOsZwq+WdYzhV8XUsK9x4Vg0VgwxGWS3WvTYeFdHiVFcK4qLDK320vxVfyfPR9XapUyY\/xlECVKSqkVRqC6zrLvjTzFJC5WmJRn\/cZZTAq21rY01VStPGa1VSlK2NJZrRpaiDUXIlEDVAoglSmqpqQLUCwe\/I8U6SlWCWD1AmxccaaqESwUY2p2QIYbKFCmNYBXONNAy+mv3AC4qlJopaCqsRspeKfp9JYQ01irjBTlMFZcqMwTURRT+6k6LhkSmoNTT1pYUrIz2WotgRiMSARgGBge8+4Kgt6Wu1shYqADjjeUYYtp6iFmmXyrLzg+\/wC6hBGp3ZFAqYmlRKUlqGm8CthSC7XXOktS8dYzbFVbV6KuH1AUipxZtiqtq9FXD011opvx6qPm6\/zKhzRFC2XOxAxyQK+wJ\/NxxhMGYUQ4WGVvtpfiq\/k+X1loCVPYWM+bPVDALeYCF\/Q1ZubLesDpcVVER\/XwT6ZgA2i1bCpUDCCqCpKzYhhrfUKguW9cxKhuQQCawM2UrVKuQlYZLFqUwNwAaskFWmJtQzetlrC10sKgszggMLVKi2HJFhVChKlgKlJYK6Y71AFVMc6c2JNyqBXW29YK83rNyzetkf66lRWiVVwdhrLLrzXWeQLbrItciLWCrvTFa1KC1gfpSsoBregr+gqhQrpYN6M4mxBENw65Ai0o+0EynvLG7A+feAeOD7\/vuJ8lQTiMshlMhmTYLVuWdYzhV8s6xnCr4upYV7jwrBorBhiMslutemw8K6PEqK4VxUWGVvtpfiq\/k+e8vPWesTwZYzFpi0s0sYvpMhMxCfHr5vLy8vLyn6y0xji3yD1hFvkEI8X8jwYDKg8AeLeBPbwZaY+Ly\/yW8CExPU+k9JZZYeAplysLE+b+BMwJcHxSMEekGnWeGhaaTCIq+pEcnzbxacIev7qEEandkUCpiaVEpSWoabwK2FILtdc6S1Lx1jNsVVtXoq4fUBSKnFm2Kq2r0VcPTXWim\/Hqo+br\/MqHNEULZc7EDHJAr7An83HGEwZhRDhYZW+2l+Kr+T+m8DQZy1SYtLmZNMmmRl\/GJMK2l4GtM56y8DS\/yUR4MqWl\/IYrC5Mxlj4WN8\/\/ACmY7egl\/Fz4Ex8XEvL3mMtMZhMfmpe59\/HpLwGAQj5BLejXgaf8U2IMymcJvM7BnuQfBhbyIDOHP33E+SoJxGWQymQzJsFq3LOsZwq+WdYzhV8XUsK9x4Vg0VgwxGWS3WvTYeFdHiVFcK4qLDK320vxVfyf1Ux6qIY0PgQC\/gS8MI839LH5RKY9Ibw02aGky+PaE3glzLmCLH+Y+Faxd1Ig8W8pLeby5mcyEyEuIQIVlpbx7TKZy4lx4ED+mUvAZ6RRPWXEKxYYjTKB2m6M+ctPaXjeB7NA0BnA9\/3UII1O7IoFTE0qJSktQ03gVsKQXa650lqXjrGbYqravRVw+oCkVOLNsVVtXoq4emutFN+PVR83X+ZUOaIoWy52IGOSBX2BP5uOMJgzCiHCwyt9tL8VX8nm0tLS0xmExg9JtmyZXlpjLQWhImULTImXMvLy8yhnr4vFmz02QvC5mz5b+BAY\/k+B8loJYQz38GJ59J6T6ZiphSesuZeZS8v8tvNoPlR7RmhlzFjGJLzKXhMvLy8v4Y2nv5\/x3v8AvuJ8lQTiMshlMhmTYLVuWdYzhV8s6xnCr4upYV7jwrBorBhiMslutemw8K6PNylbkiGVvtpfiqj+S39PrPXx6y5l5eXl\/A8AzKXghIhigy09IwgMXxeHxeD1IQQ4wJMVEJEW8JSzkQfI3v8A8Hm8y+QS8v4KmWaXYTZM5cf2n3PgQwHzaIPRx6w\/KZeZTOFr+Ly8\/wAdP3UII1O7IoFTE0qJSktQ03gVsKQXa650lqXjrGbYqravRVw+oCkVOLNsVVtXoq4emutFN+PVR83X+ZUOaIoWy52IGOSBX2BP5uOMJj9NO\/gyt9tL8VX8nz3MvLy8vLy8v8oh8WMtLi3gPL3h8C1z6TIy8v4vBeFml5m0yMEyMY38WlvF\/kvL\/KsB+awhQQpLH\/QE\/wCkiA+MxGf08Xi+Ly8NvHp8lvH+M9\/33E+SoJxGWQymQzJsFq3LOsZwq+WdYzhV8XUsK9x4Vg0VgwxGWS3WvTYeFdHm5StyRDK320vxVj\/JlLy8vLy8vL\/2eni9oDf5rxYZ7S\/yqvr6CfdMbQkS15YQmE+R4P8AUPNvOTTJpmZsmUv\/AEestLS3yCHyIZf5MoCZf5rS3n\/F+\/7qEEandkUCpiaVEpSWoabwK2FILtdc6S1Lx1jNsVVtXoq4fUBSKnFm2Kq2r0VcPTXWim\/Hqo+br\/MqHNEULZc7EDHJAr7An83HGEx+mnfwZW+2l+Kt+X5\/T\/QvL\/KDLwnxfwBLeMpsmZMymwzIzKXv\/oXMvMpl4tCIV\/qvA09TLGH+geQYYPF\/nvCfP+Mn77ifJUE4jLIZTIZk2C1blnWM4VfLOsZwq+LqWFe48KwaKwYYjLJbrXpsPCujzcpW5Ihlb7aX4k4tCovS406XGnS406PFnR4s6XGnS406PFnR4s6PFnS4s6PFnR4s6PFnS4s6XFnS4s6XGnS406XGnR4s6PFnR4s6PFnR4s6PFnR4s6PFnR4s6PFnS406XFnS406XGnS406fGnT406fGnS406XGnS406XGnS406XGnS406XFnS406XGnS406XGnS406XGnS406XGnS406XGnS406XGnS406fGnT406nHnU486fHnT486fHnU486nHnT406PFnR4s6PFnR4s6PFnR4s6PFnR4s6XFnS406fGnU486fHnS406XFnR4s6PFnS406XGnS4s6XFnS406XFnS406XFnS4s6PFnS406XGnS4s6XGnS406PFnR4s6PFnR4s6XGnR4s6PFlOhSpH91CCNTuyKBUxNKiUpLUNN4FbCkF2uudJal46xm2Kq2r0VcPqApFTizbFVbV6KuHprrRTfj1UfN1\/mVDmiKFsudiBjkgV9gT+bjjCY\/TTv4MrfbS\/F3NU+Iz4jPiM+Iz4jPiM+Iz4hPiE+IT4hPiE+IT4hPiE+IT4hPiE+IT4hPiE+IT4hPiE+IT4hPiE+IT4hPiE787878+IT4hPiE+IT4hPiE+IT4hPiE+IT4hPiE+IT4jPiM+Iz4jPiM+Iz4jPiM+Iz4jPiM+Iz4jPiE7878+ITvzvzvz4hPiE+Iz4lOLyexK9TVT+Iz4lPic+Jz4nPiU+JT4lPiU+JT4lPiU+Iz4jPiM+Iz4jPiM+Iz4jPiM+Iz4jPiM+Iz4jPiM+Iz4jPiM+Iz4jPiM+Iz4jPiM4vI3n99xPkqCcRlkMpkMybBatyzrGcKvlnWM4VfF1LCvceFYNFYMMRlkt1r02HhXR5uTENl4MrfbS\/FV\/J\/cATNdSa6k11JrqTCpMKk11JrqTXUmupNdSYVJhUmFSYVJhUmFSYVJhUmFSYVJrqTXUmupNdSa6k11JrqTXUmupNdSa6k11JrqTXUmupNdSa6k11JrqTXUmup41uYKdSYVJrqTXUmupNbzXUmup8n+L9+d\/8AG\/8AE\/xvv+6hBGp3ZFAqYmlRKUlqGm8CthSC7XXOktS8dYzbFVbV6KuH1AUipxZtiqtq9FXD011opvx6qPm6\/wAyoc0RQtlzsQMckCvsCfzccYRkYpTv4MrfbS\/FV\/Jx6G6dMZ9Vdumjd+KqluHaHgwcRdVOmatR+MoLcMCHhCdRdaIztwQQ\/wD5VT8i\/bMvnqfk8f4z353\/AMbwKKcqjyFTM8KoJpYUqlFqa8RRpdA3F5FDKu3FYI9B0nVqbanHZF6lS3MVUrazqp0aeqvRFP5aKbKjJx1dKT1GqUqlI9XkY\/0f433\/AH3E+SoJxGWQymQzJsFq3LOsZwq+WdYzhV8XUsK9x4Vg0VgwxGWS3WvTYeFdHm5MQ2Xgyt9tL8VX8nFrrSHZoJUPI4xqVeQtSnUrrt7NNS3JoVV7dEpSqrSrGtSVuzSEbmUb9yiyDVs4Fs\/\/ACqn5F+2YzE3CmYxVt5q\/k8f4z353\/xvHAdUq8aqKfIpilRrKUrcXltTKceoE45d2jNx35OxRT4jI9Lj8hc6z2o1tdZuayvWwGqxqcLl\/TT+SkENSmOSr0TSZKpwWpSrNyNTPDRYJVptSf5f8b7\/ALqEEandkUCpiaVEpSWoabwK2FILtdc6S1Lx1jNsVVtXoq4fUBSKnFm2Kq2r0VcPTXWim\/Hqo+br\/MqHNEULZc7EDHJAr7An83HGEZGKU7+DK320vxVfyf3Uqz0j3q071ad+tO\/Xnfrzv1536879ed+vO\/Xnfrzv1536879ed+vO9XnerzvV536879ed+vO\/Xnfrzv1536879ed+vO\/Xnfrzv1536879ed+vO\/Xnfrzv1536879ed+vO\/Xnfrzv14Tcr\/kGA+ImfEWnxIz4kZ8RM+INPiJnxFoTc+P8AGe\/O\/wDjf1nkuafyqzLCST\/o\/wCN9\/33E+SoJxGWQymQzJsFq3LOsZwq+WdYzhV8XUsK9x4Vg0VgwxGWS3WvTYeFdHm5MQ2Xgyt9tL8Tcau7dPkzp8mdPkzp8mdPkzp8mdPkzp8mdPkzp8mdPkzp8mdPkzp8mdPkzpcmdLkzpcmdLkzpcmdLkzpcmdPkzp8mdPkzp8mdLkzpcmdLkzpcmdLkzp8mdLkzpcmdLkzpcmdLkzpcmdLkzpcmdLkzpcmdLkzpcmdLkzpcmdLkzpcmdLkzpcmdLkzpcmdLkzpcmdLkzpcmdLkzpcmdLkzpcmdLkzpcmdLkzpcmdPkzpcmdLkzpcqdHlTo8qcGhVpTlI1Sh0uVOlyZ0uTOjyp0eVOjyZ0uTOlyp0uVOlyZ0uVOlyZ0uTOlyZ0uTOlyZ0uTOlyZ0uTOlyZ0uTOlyZ0uTOlyZ0uTOlyZ0uTOlyZ0uTOlyZ0uTOlyZ0uTOlyZ0uTOlyZwqNSkf3UII1O7IoFTE0qJSktQ03gVsKQXa650lqXjrGbYqravRVw+oCkVOLNsVVtXoq4emutFN+PVR83X+ZUOaIoWy52IGOSBX2BP5uOMIyMUp38GVvtpfio\/jvLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8\/wC\/vuJ8lQTiMshlMhmTYLVuWdYzhV8s6xnCr4upYV7jwrBorBhiMslutemw8K6PBVyUVMjDK320vxHj5jpNBwo3DSdIwcJYeLTE6izp07nh079OnOosHGpTp0o3EpRuOBNaQ0mmtpi8s0s0+qWaWPm3y28W82MxaWMs0xaYn+q\/9tvFpaW8W8WlpbxaWEtLfLaWlpbx6SwlpaWlpaWlpaWlpaWlpaWlpaf473\/dQgjU7sigVMTSolKS1DTeBWwpBdrrnSWpeOsZtiqtq9FXD6gKRU4s2xVW1eirh6a60U349VHzdf5lQ5oihbLnYgY5IFfYE\/m44wifTKakJDK320vxUfxyo+CJ2HWlVYs4rIlPfUQsEWhXqGo4YxzWR1WoDxnZ5yGKU2rYUMOQVoVM\/AxM\/b0vYXK05gljSSw41JoeLRE6dKdSjDwqU6NMzpUgTxKMbhidOpOmZ1LQJWUEV4RXibIEUgpShoUGh41GdRZ0hOkJ0xOmIeGJ0hOms6azprOlOhUnRqzo1Z0qs6VadStOpVnUqwcSrOrUi8VoeMY3GadWpF4tQTrsYeI06ladSrOpVnTqTo1J0ak6Lzo1J0nnTadQzqtOrUnWcTTNSQcdTOugnXWaJoho2mBmJlv6PWes9Z6z\/He\/77ifJUE4jLIZTIZk2C1blnWM4VfLOsZwq+LqWFe48KwaKwYYjLJbrXpsPCujwVclFTIwyt9tL8VH8cqlMBRdRTqvsr\/i434eWxtWP0o2aV\/zmcWcn8R16NNZBRrFzFRVJ+6L7\/8ARP8AiR\/f1t\/w+wje8\/6vuJ+v6y0t8mIManBTlpYzCNxw06aTpidbGa2hpPFWoJ\/NMmmxZsUz08W8lTAD5N5nWyFQTNLl0mdMTs0RN9EzYkNQQlZTIELLLmfVA1QTOpAWnrCzTN5nUl6k\/klnmDTQ5h4rzqPOmZ0jOkYOIwmmpNFWdetBQrCa60NKtOHTZD+6hBGp3ZFAqYmlRKUlqGm8CthSC7XXOktS8dYzbFVbV6KuH1AUipxZtiqtq9FXD011opvx6qPm6\/zKhzRFC2XOxAxyQK+wJ\/NxxhE+mU1ISGVvtpfio\/jjqHVU5FOU6JD1FzSkhSmKTbyLihTemKlJmqTS6u1KrUNSktRAvJUUqWHhBUuwlvpUSxuAZY2WN7kenrCIPZgZ6w3yHuAZY2t9PrLmXb+m39JF5qE0CaprgSWlphMJqmqYTATATATBZgs005qSa1mtZrWYiWlhPTx6efSek9Jb\/RHv++4nyVBOIyyGUyGZNgtW5Z1jOFXyzrGcKvi6lhXuPCsGisGGIyyW616bDwro8FXJRUyMMrfbS\/FT+lby8vLiXl5cS8uJeXl5cS8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy4lxLy8vLy4l5eXlxLy8vLy4l5cS4l5cS4lxLy\/i4l5cS4lxLiXEvLy8uJcS8v4vLy8vPTx6T0lxLiek9JcS8vLy4lx4vLy8uJcS8vLy8uPF5f6lpLGpXZKagrSwUUKd9LW1EKiKlSoA1PKOl44yQU8atOkynrjWUJVxkgp41adJlNOngMf\/wAdqTMzU\/5FpfWlBbCiCVT0NG4FJstQFSimuJ9MQWDUqTP6WrfbTqjXuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWblm5ZuWbllWqCP\/\/EAD0RAAEDAgMEBwcDBAEEAwAAAAEAAhESIQMxQSJRkqEEEBMwMmFxFCBSgZHR4UBCwSNQYrHwJENTYDOC8f\/aAAgBAwEBPwHL\/wBHa1zslQ7ePqnN0P8A6RhvDRcJr2gzf6LEMunzCd\/P93bl+idp6hH+e4Ct1DqAQErZ3pwhNErZThrp1NEqGbyiwRIP9gbl+idp6hH+e4GaIKFk3qbkUzVU+afoj4UBKh0eXVh6qgobIum06oGdEBtwi4A5J45o2yCcQR5qzR5qzh5pgEGU0g2hAiaYUCuFTtpzdoeadFQCq3Cye3JGAJhNAMuKBmxCcIPdNy\/RO09Qnfz3AzTupuaIQsCmKCj4boXbG5X81ekz1Yeqm68TfMJgtOqaXE+S\/wC4n5lYmi2m+ad4ZyKfcAhYdgSVh5FMF\/ko2vmnDalHU+SbcDyTbvRJ0T9E\/IJmSlydn3Qy6\/x3v36nfyFEqhUeao81R5qlUqlQoVKp81feiPNR5q+9U+ao81feoO9dn5oNIyK7PzQaRkUGkaqDv5KgzMosnVFhOZ5IA7+SLScyg0jVFpOqDY1QHXCpO9DDjVX3otJzKp81T5q+9U+apVHmqFSqVSqVSqUOv8d79+p38jrurq6kqSpKqKbg4rgHAWdYbQUO87ql+530QY8upEzuXYYsxG\/9w0UO87oyM5CofAdmDlH2RDhmCFDvO6cx4MGZImBdbUTeN6FRykqH7nKnE3ORZiAA3uYF7\/Rbf+VlJ807DxW01A7YkLa87ZqnE3O5qH\/5IB9vFfJMwnm0GYlR5Ig7jdUu3FMY57qW3KdhPBgg\/K6pdpKpxJycix8ZFOZiNMEOmJjyTGYjwS0EhoklPZiMMPkGJzUlSVJUqVKlSrq6uj4D6FSd5C2viKv8RUn4iqnbz9VJ3n6ra+IqT8RUu+I66q\/xFSfiIV\/iK2vicr\/E76ra+Iok3u7yumeEap38hb\/dhQoULo7iOjYot5fMXRxMLs23ERhR6g\/wqm1NuPG7XyWG8npcuP7jfyCDhsXGWJ\/tHFth0UEQzM6zkPNdKZLn4jXAgU2m4ldGI7PBuLOf\/Ke5paRUBs4f+7qRUzJ0OdrfJV0k2FQwm0X8VOaf\/UwS2WNPaWg7Jly6KKOkgOjZqB3J720naHhwtdzrpzwHNyO26b6QmuYKJcD\/AFHwSd8olzsB7cR7apEGfSxWA3sulNa8jZJE6ZJzmzcio4LgL6rExGkYvZOa19Yk6RF05zYFx48M5rEe2HbQ8GLr52XaNOIwNookEGb5HL0RcN4\/+PEHNdIM4eHe8QQP9q3ZgSHWw\/8Af8I4v9TEyMNFH8rBP\/VSac3ZZLDgT4Wy8GxRf4KXNAGIax\/9k3FJxMaq7WgFu7ZuiQMC\/wD4uadiND8Utgu2CL5oPacEwW+DEsDqulEnEEkHYbkZ07wZKhu4fRUt3D6Klu4aaKlu4fRUN3D6Klu4fRUN3D6Khu4fRUt3D6Klu4fRUN3D6Khu4fRUt3D6Klu4fRUt3D6Klu4fTqdp6j3IUdcKFCgqCoKgqCtqIvGcKCoKFQuJHojUc5PqgCLixCgqkqkqkqCiHG5kqk7lSdypO5UncqTuQ92ycEwuFpIBzGic8kRJMZIgqkqkqkqkqkqCoKgqCoKhQoQ6\/wAd+7T1HVKlSpUqVKlSpUqpVKpVKpVKpVKpVKpVKpVKpVKpVKtVqtVFVIKlFqKnqLlWqlUqlUqlUqlUpUqVKHX+O\/dp6j9fChAKlQggUeqOohR3o6\/x3v36naeo\/sACjuj3o6\/x3v36naeo7ioAwTEoEH5e5W2YkZx812rPiy6yQ0SbBC9x3Qc05GYQcDYG406iQLmya9mjmn5o4jPiCqaTEifVVs3hFzW5kCd6D2nIg+h7iFCcQMyB6+7b3\/x3v36naeo7jEYZrZ4gIG5UYkVbVTjtRE23KMUAeO7t8mFGKQ3x+KTfRMaQLuLvVMwjWZEgEnLOUWYpaQQbukXGSIxaiNq5sQcgv6tU7R2tTAhYtdOxM+RhEYs5P0gTHMFYlZZs2coxSHEVg2gEojG2hLjl+YTmvpNJxDJyJTqmAy5+0bRoq3UudJEECx+qLcWDFV4gVSjh4sSC7TZ\/KpxSP3iTlVf5FYLHCqQRJ1unYb63FocJyIICe8gm7p8N7X3iE5p7Ii5NKbgvpmdoAw2AOaZh4gc2W2ayM9Shh4kAQ6QCLxSJ3LDY6WS2zBeeULGa91NOhklU413RtEQTr8k1uLP\/AHKYObrygMcRJcdk\/hNGNBirLNx\/gqnFpdHaaRU66px7iX\/t1j1T2YpkXMkRe0aqMa\/iir9rrx5KnGpd45MBt\/rKdh4wbIc7TZ8vVFj3MdUH3cIBv9uuFChQoVKPX+O9+\/U7T1C39w7GDcSl1hEyu1ZvzEo420A0EyJ+XopEVZCJQ6Q0vgRTEkrt8P4svIrtAfBDiIsu22yKTDYv6pzg0SbBdsyYuLxlqu2w5pquu2w9+sLtsPfrC7Vm\/WFhvqqtFJhdp\/ULI8IqlYOJXm2mZjdbzWI6ktAp2jG1PKEMTDJgGTlkY+qe5rIqMSu2womoRMJ2ONmjaqMJpkTl6rDxw7OGmYhdvh\/Eu2w9\/knY7ZAbDpMFYj6GzmhiiJdsxnP8Jr2uEtuF2rN+sfVdth\/FrCbjeKuAGuhdthxNSOPtNiKTmUx7XiWmUceKjY07p52WHiVC4g259UKFChR1yj1\/jvfv1O09Qt\/cOwqnVVOFotuR6ONCRs0o9HB1PhpuuzFFF4iF7ODm5xtC7Cxqc50iPkmYQa6RuiEejtJJk3Mp+GHtgr2dpmSSSZK7G\/iMTNOkpmC1vnrfemYAb5nO+9Do+IXAuIMGTGsZWTcKmbzUZXs4qLpdtCDlksPorWEOkktED8p+CHlriXCi4iPsm9FAcDUbGbW+sLFwG4gAM2M2XsYEkOLTUXAgZTon9Fbs3JpnO8znKZhhjaQvZhv1Jy3odGyl7tjJDBaJnakzfevZxbadDTIWIwPFJsuwbmCQZmQmNpEXM3JKHRxPid4qvmj0duYN6q7rsRoXA1VT5lDo4kOqNQ1N5RwGmJvSmYdAgEn1R6O2HbTtu5yzTejgTBILtQg1QoUdUKEWqlHPr\/He\/fqdp6hDX9FjGMNxmLW9Vgn+kDnaT6rtnOcwktifC0nmm9Jef\/HkTF5Eb1h478UhhAaMRrrjP1WK9zcU7Tow2Wi998fNPxCMHtBnTOSPSsQSbGGtNhsyUemOEkUADR01FHpeKMgx84faTu36rFxnuewyR\/SGJDd\/prC7QnDq1pm6HScQ6N8JP0XtD4HgZadqb+kJ3SMQSKW2YHeid0h0nJuUTN5T8Ytnw5abV9xTCe1uf2TE6+80IhHqCHvwoWJ4j1\/jvfv1O09Qhr+hc9rYnXK0prg4SLg9T8RjInM2CbTmIvqFAzgEj6rDxGYm0BkYuEE1o0gJ2NgBxDrOGewfsjj4UZggtq9Qu1wSBA0kCnTyCOKzfMir5b0Cwt7QXAGcXQx8KKjlMTGq7TDeXMzp8Uj7ogbhbJHCwzcjO5UJz2tz19wJqIRCpUdY9\/E8R6\/x3v36naeoQ1\/Q4jXlzSDYZhDBxAGi2ckTyXYPhsgGJJvv8kcHE2PDszl+VhtLcOHRrksHCc6l0BtLpJM1H8L2fEgCGuFRJFREyuyxGscCyus\/scZC6I10vc4Rk0WpmNYTujvdjPdU5jHAeF0Ewn9FfL6C2lzAwTmAndHfsWa8MZQQHFsr2fGBkEXZTnksNtLQDcgXXYYhwi00ycXtM\/wn4L6sQiIeRreBp812D6TSA2SLA6BHAeQRDdp056J2AY2dTJE2\/wBLsn0xAs+oRb3AmnqI6igqVSh7z\/Eev8d79+p2nqENfccaWk7hK7TEFJIZDjk3NDpDCQINzAWNiFgaRqYylNx77RbREggQfoV27cyHDZqyRx9guANhqmY9tvOAbeekL2hoBlrxGhG9e0MyvMxCa8OmxEGLr2hu5xExIFl7Q3c6Piiy9o222LWEE3F7JuKHECHbVxZYjyHNAMVGMpTcdrjAB3SnvcMRjR+6Z+SfiBgk+idjOpBZbajaz+WaZ0hwntGnxQwAbR+S9sw72faBlqdF7W2HGnE2c9lO6S2mqH074Rx3FtWGNf3ZH0undIcJEgurDfDlP+03pDhVXcTAaBt\/RO6TINApc2JrtE\/yhjuFReDSBbfK7dsTDs4jU+iPSGXkOEZyE7FMGgbTY8WSdjFofcVACBELCe905EDdmu3dAMztUmWwu2Lmzh\/FSav\/ANT8VwHiuH0m2S9oJGxYg\/u\/hM6Q8VHEaaQQG2vf6LG6WY2GkFrg1weN\/oj0hxBoEOaRIxBGfzXb01VmS0gQBH\/PqjjupllrxtZ\/LNM6URPaA3dDLXPyTMUPmARSY2hCZiu7V7DENAI+aB6p68TxHr\/He\/fqdp6hDX3W4LGmQLpjGss3\/acxroqExkhgYY0\/4UOjsExN\/Ndgy9pqEFdgzd5TrZdgyIvnJvmn4bXCDomMawQ1DCaDaRJmJsuww93ymy7DDmY5lMwWMMtz9U7Da4gnMZXKGCwEG5i4kp7KnNdMFhT2Bwgz8jC9nw84O\/Nez4f+U75uvZsKIg3vmUcNpEGo3m5XYsikTEzE2+i9mw4iDnOa7Fl7TVcn0yR6Ow51E7ybr2bC885zKOAz\/L6ldhh+c75uhgYcG3izuvZ8OIg3M5rs23\/yFPyRwGlobexlDAbEXO1V812DPPOV2TLyJkyfVHBwyZg5zmUcARsuI0M3tuum9GbtVXqOQsLL2fCIgjWc07AZfOTcmV2DJyO\/NDo+Gc5PnN7LDY3DbS3LNMZGI98zXHyhBHqClP8AEev8d79+p2nqENf1A6ih7hRPvT7oKCpRagboBUIdRHVKdn1\/jvfv1O09Qhr+pnqHuO6h3YcqkVSgVPVUi5Eqfc\/He\/fqdp6hDX9FPeSj30qVKlVBF6lT7347379TtPUIa\/r4UKFHeT3n47379TtPUIa\/3r8d+7T1CGvu7\/7Dp6W6ty\/5z6\/t7v37n8d+7T1CGv8Aevx37tPUIa\/3r8d79+p2nqENUzxCxN8hmU4Ah9NDgLgAUubyuqv6NUMntKfA3KPRYOGKNoAnFmD8MZH6rA8dDg2wdm0G4CbtYeKSBIpiABr5JjW0MJbV\/VMwLkQnAFjiKHgXBaKS31EXVoMzOigckQJ+SpF\/UIAVqkH6xZFoAQAgWmVSI+SoCoHP+z\/jvfv1O09QhqgYuNEcZxnwirMgXKrNNGk1fNOeTH+IAEeS7V1ZfaT\/ACmvLZiL2IORRxXWybRlFkcUkEbIqzgRKmxFrqvLyVd\/VVqq8oPhF0hB6rt8oVarVQ3D+6u09QuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLkuLMbl\/\/xAA7EQABAwIEAwUGBAYCAwEAAAABAAIREiEDMVFhEyJBEDJSkaEEMEBxgcEgQtHwFCNQYGLhM7FjcoKy\/9oACAECAQE\/Af7HJAVTdPRA2kdkqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlPbPVFpOiaIbCH2\/q7s\/c\/r7kZFD7e4Kup1RMdhNwiYXNommU50fNc6Dpt17HOhS\/QIPMwbf0B2fwQ6ofb3DsimkQjfJPy7Hd4J\/RVbJiF3FEwuWd+zEyCrC7zrdE6o5IiLgonklBpIzTD6IXzMJoI+Su46BXac5CfMiE4Ft5RBiqUSSyUXcg3TXcp2QqplRqbrDM5oSeqcSIaERFwUDI907P4IdU37e4dkU0COx+SBEI3cE\/opGqb3rLuu+atsrVCOzE6fVQF3XbFPzjIJwaBbNH\/jTO6Fh9Vyu2Te9GYTLEgp9yAE\/vBYhsi7l+iaeWELkDdOsSNU7lYEAOqw+qZmU\/vIhqbl7p2fwQUwq9lXsuJsq9lXsq9lXsqtkHbKvZV7K2iDgOiLgeikaeqr2VeykaeqqHhXE2ReDmPVcTZFwOY9UXA5hS3w+qrERFvmhiR0QeBkPVEjT1QeBkEXA9PVBwGQRdPRE9koFVDRHEkRCtog8DIeqDgOirnopGir2Veyr2Veyr2Veyr2Veyr2VeyPwQ7LKytoraBQNAoGgVI0CpGgRcwGNNlbZcuyJaBNlUz9hW2Vtly5WsuXZW2QpzsuXZcuy5dly7Ll2tsuXZQNAgWGYiyhuy5P8Vy7I07Jzm7dgUhEgXQIQI2UtjogRPRCkibI0AgGLptLhIgqBoFA0UDRQFAUBQFAUBQFAQ74+YUDwtK5fCFbwNUNtytVLZyb5KG35W7WXL4BmoHhaob4W9OihvgAUDwg\/RQ2e6Fy+Fqt4W+S5fAM0ALcrd7LE7xiyC0\/DKlSpT\/+VqpdJz\/NKgwfkE8fyoGyjP8A9mqnOZm6wzYNIgwsQXf9EAZy8S6Hp3VExpUZ2TeV8wSKfqsa+HbrCAM5eP8A6QFjnkFBM26NQgYgIBiMlic2FI6wVBjatNaZbWCRTbzQB0PcITQZFvzNVJoOc6fVR\/8AoJgu5dfNRYeqd3EfsozkGYsi2AyOuaHe\/wDooNkNmYgqDxLz3gsLufU5+8Oard4j5qt2p81W7xHzVbvEfNVu8R81W7xHzVbvEfNVu8R81W7xHzVbvEfNVu8R81W7xHzVbvEfNVu8R81W\/wAR81W6e8fPfsGRWnyUhSFIUhSFKkKQpCqCqCqCqCqClqqCqClqlqqCqCqCqGqqGqqaqmqsaqsaqsaqsaouGqMdh\/A0p9J0lNEFBwVQ1VQ1VQ1VQVQVQUhSFIUhSFIUo\/Afv17BkVooUKFChQoUKFChUqlUqlUqlUqlUqlUqlUqlUKhUKhUKgKgKgKlFVIFQo7A1UBUKlUqlUqlUqlUqlUqn4H9+vYMitPl7yVKlT7uVKlSpUqpVKUUUPwAqVPvD8B+nYMitPl8ZPZKJ92CpUqVKlT+M\/Afp2DIrT8cIj8FJ0VDtO0AnL3ZkZ2UHr2Z5XRa7QoNdoVB0KpdogCchKLSMwR7iVUhJyupRcg5Si5VFAqfgf07BkVp+Nrh3XZKpsxaBlP3XJfLLSyll+7laycQTYQnPFNsyIQcwGQR3dOq5I6ZZELkjpkmUzzZIUR+VMpqvkpZIyOtlOHbLqg5s3psOiBDjYNtr1UCQLZE3Hkg\/Dkd22doQxMOYIHzVeGD+XLOLLGeDTBBgdE3EZS2S0xnZNaLTTrrbdMPPPSUXtmOhNzmi9sG+bpRc3O0SDbP6pzhDoOZWGWiZ6qcOwmwy0RLI\/LM9B0X8vpGYR4e2fQKpkiad4Cqw7Wb1QcyxtYX1lTh7THUWU4UjuwM7IPwpghot3t\/kg9odYt7pk5KE5NKqVQVQVYVSHwQyKPT3Aw5bIz0VDtNkMOxJMdFF4RwiGz1mIXDfoqCO9YLh8szcoAmwVDvuqHRMLhu0VDtFQ7TdYjaYvMiVRyB098xCxmUZOmLHW+yYKg483LpHqix4EkR9RPkmhzu6JhcPEmKbxKbhHmr5aVkYPon4ZGV1w36Lhu0QwzEm1pTW1GFQZtdEEWNlQ7RUO0Rw8qbyJXDfouFynOodE5pbY2XCmkXFWv2WIyk2MjsD049k9koFQm5fBDIrT3AfAiAet0MU6A3lDFI6dZVZqq6rinoALyuJlAAhOfIjeVxTHyEJrqTK4p6AZQuJsJylOxCdvkn4xO3yTvaGUkNBuIE9Nbp2LVFogQv4gw1tLeUyM1iY7ngtiATKZiljS0NaQ7OZTvaC4EUi4i9\/JYeI5kx1EJ3tBP5QRABB2WHjv5rDmjK0RonGo1Fcb7eiONmKRzZriE7fJcQ3sLprqTK4h6iRCcZKOLsMoXFPpSuIdBlCOLmIEaIYpH1TnVGbLjOkGBy2T8dxiQCG6qT7iSsPuj4IZFHp8FhiXBP78ZXhcIBrwJ+ZR9mZ\/5MwLxf5J2A3DFYJcWOFjksPDacMcred1+nqsPDBxKDqh7OwgC4knreyHsrbd695EQh7Ow5lzeemEzDbQRAM4hbJ0G6awB8bxZHCbqc4XCb\/k6\/Tp803CYYMm5IQwhAzOsJuGDrn8rJ0UWHXOPwlOKBQUItCLVHuMPuD4IZFHIfAhpOSIIMHsDHOmMgiDkZsr5Tb0T2OYaT6KESUMLFIBGRy5guFiTYRePqgzFBv1ME1dUGO9Y+qh00ddEcLEkgZxMT0VD2w7KcoKBKrdr2BpOXT8BTkECq0Sp9yzuj4IZFHp8C0tgg5nqjiMNWd1xWybm+yGI3mzueqcQXyNViYgFTbmbdIC4rZJuJjoq2l0h1NI\/MF7QW8rQZjefVcZrcNoADnN1EhNx2Q2oOlpLjpKbit5rlpL65pBXFwyMjZ0\/NOMklcVlZN4opyTcRsM\/xGmq4jZvJgZwhiNkZ2EZIYuvQQLKtsm5u2PwFOHZKnsIQKKH4m5fBDIo9PwASQNVSwgxVI1yRwnCcrLDaHTsEcLKJmbjNcM7Zwhh8wBOeidh35fVcI6tXCdtlKc2NPouE7b5LhHadJuuFynIumLFFhGlrJrZBMZbp2GW5wg0UOOkJrC4wEMMSar26ZfVOwge6Rlck281\/Du\/xyn6L+HMgSy+6GAZiWyhhCYdp0+6GC3OIFM55p2CDFNrSSTyoYMEVXBypuuGDFJv10XDOo+a4TtroMFqjY+FDDBLbGL9U9rRHT5rhi4iCBIgrhgEh2kiEGDT8si64Qnmvbp90\/BaYoImJOlkz2cfmIMtqBaUMEA85kESCy\/2XBmKRAImSZQwhJqvbp90\/AH5CLNl17J2HToZE2T2NDGOH5pn6IjsHazuj4IZFHp+E4jjmnOLs0HEZdUcV2qOK46W2XEdbZcRy4js\/omvLTITnFxkovJz81xHLiO19E7Ec6xQeQIH\/AEjiOy1QdAI8Sa4jJcVy4rtvlC4r5n7IOIygfILiOmbLiuz1XEdbYQhiu2+ULjP2yjJDFdt5LiP\/ANLiP8lxXTKrNtroYhknVHEN9xC4jlW70hcR37C4mo+UWujim0dBF7lcV467IYh2tZcRydivGUeSdiFzpcnYgLWt8KlBDtZ3R8EMij0+LP4IUe+BTk4KO0dkJuXwQyKOQ+LP4B8CQoUKFSgFHwP79ewZFHIfAwo+NjspVKj4T9+vYMij0\/sf9+vYMij0\/sf9+vYMij07f9\/67NP6D19ezVfr9l\/o9n6r\/S\/Xt\/T4b9+vYMij0\/sf9+vYMij0\/sf9OwZFHojkhmMx6qOaL5aouvPh\/ZTum5C\/MPkUZ5vohn1Hzv2HEeHRU7lxTN+lk3FeMOqe883deBsEcZ5pORLHek9E97\/4ZrpMmLhHGe2zZs2r+Z3s0zFe\/Fi1MA+YTsR3Edz00vAAvHouO\/iU2iotj\/aHtGLH5btcctEfacS0D8oJtMyh\/U\/07BkUeihU+ii8qFTlsiFT6qlQj7MC57pPOIX8MKWtk8hmfmh7KLXNgR5o4E4Yw5NuqxPZw8zJFqTGiZghri4dQBHyTvZg5xMkSZjdfwwqqk96qEPZRqbAjzR9lGp7oad4XCd0e4fb+qjIryV9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9lfZX2V9l\/\/EAEEQAAEDAQQGBwcDBAIDAAMBAQEAAhESITEyQQMQInGh0VFhcoGRkrETIDNCweHwYoLxIzBAUGDSUqLiBHCyQ8L\/2gAIAQEABj8CBIBJE223rC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+ULC3yhYW+UIOAAgjvti5N7I9P\/wBHd4HiYWN3DksTrbrrd2ysTuH\/AFQEzM39UdG9d49Qm9ken\/6O72+oV6gye7CrD3oT0H\/\/AJXePVM7I9Fer1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1er1eolfE\/\/AJ5L4n\/88l8T\/wDnkrHVH86EO031Wj7LfRH\/AJa5CbbD9EadCTBidnLeVhCwhYQsIVMCb7lhCopEgTcg32dRPRGW+EdiCLwQsIWELCFhCDgBB6lhHghpKbDGXTYsA4IuIEC+xYR4LCFhCwhYQsIWELCFhCwhYQsIWELCPBYR4KwQh2m+oWj7LfRH\/lrkNx+idV7SanXe0i\/9Nie+narbSerZmEamH2lR2+rK36LRvY3bik9O1096pcysBgiyrMq7+oWNkxNx2hO5Ppw2bIbR2oBPQnOa2hkNGGm2TkmOgxDrgT0dC0j2iJAAkXgG2xPpGzZshtHagE9CYGshldoIgXHJezjYZtjvu+q0VDLfnMXiOnNMluyG3HRl21nYITfbNL9kRZO1ndKYIMimRnYQmFoNjrbCMj0p4DZ2DaWlpnKcnFCLB0Xf3gQ6GjLp36h2m+oWj7LfRH\/A2RKuVyu13a4CkjVcrlcrdeyJWSyK2hHu4fRYeIWHiFh+vp\/wByG4\/T3ATlcqs7v7BjO9BouH+GFl4\/ZFDtN9QtH2W+iP+A5CyZWHiouK6x7oRQ3onoWHioIhdeR1dQvXQ0LZHitoeC6WlRll7uDj9lg4\/ZWX9C9oL8\/9+5Dcfoqq7Oja\/wC8I7eX6\/q9EV39v\/uorzv2\/wDugK7u3\/3Q27h+v\/uqq7Oja\/7wqq7Oja\/7wjt5fr+r0RXf2\/8Auorzv2\/+6Aru7f8A3Q27h+v\/ALqquzo2v+8Kquzo2v8AvCO3l+v6vRFd\/b\/7qK879v8A7oCu7t\/90Nu4fr\/7qquzo2v+8Kquzo2v+8cP7bd\/0Oo7uaHab6haPst9Ef8AAd3JuoR7wRTd6du1QL1blq3qnIa4iULIj3wnKkXlW7R61GxPcrqT1ItOSJcJUx3KIbPcqmWRlq2rSo2Z7ldB6kWnJVOwq4R1\/dXDu+y\/SgDcroUQO9WCCtq3pJVwjqUZZIGERkiXWoFo3oBEwrcIVwjr+6sDTuVTMstWFWxvKHs47lt2lRDZ6LFZslQb\/wDEchuP0Rp0bjBidnLe73ac79VGYEqShsltWEnNP\/RafVVbuNnuP\/RafVVbuNmunqlBxY4MPzWfQ65GRI8LEHC46nTZTep6LLRHUrMiR4WLoQsLpMAD7wiILXDI\/adV54ah2m+oWj7LfRH\/AAHdyFsQsXBTeVHzH3Qim71bcvk4LYjuUNPdqbuTtQNV6xcFMz7t5V5VlnWqW4VVEqkCFhPgodl06jvVAzvQI1N3ogK6dyp6OlSbyo6FPguo3ohNRcFJvQPUiMgnBNRb0IFAIhE9CDelRTKsG4XoENd4I65KnoWzf1LCfBQ8Gy6UHd3+I5DcfonVe0mp13tIv\/TYnvp2q20nq2ZhGph9pUdvqyt+i0b2N24pPTtdPeqXMrAYIsqzKu\/qFjZMTcdoTuT6cNmyG0dqAT0JxYyltIyp6U5jbJnqtQFJ\/VNkc1DbBpGFoy3JrA10yJkERBk23KQ3MyS0gj99xTZxfPsETvdNq0ctytBZWJ6wLZVMQdJo6Y6wmsDXTImQREGTbcpDczJLSCP33FNnF8+wRO902rRGINzu\/wDhDRBrqy2m1pHEp3dQaC49zpEKYl0i9p4PFynSNqYXOgRhtv7032mjJbTAFJsdn\/KA0zaz7NuVVtqaNIKqmQ4ZyE6wvbIxNtxbuhNcG7XtHSY+Xa4Jktl1IsLSfAjCUyx0B1sT0HoWkpa4sMYpnrv2oT7IbZENLBnkdY7TfULR9lvoj\/gO7k2kwsXosXuHWU3eE7cdYJvW9Bb9UAqHHJDf7uI+KxTvU+IVlxuUeKm7rVjVVELuR3obvch1qvjerbUBkjqGpqdqbuTt6cmrehvRKBR3rcqndwUcAoo4\/ZH3HKSririgB\/iOQ3H6I06NxgxOzlvdrBOVyqzuVOd+qjMCVJQ2S2rCTmnEj4dvDJVbuNnuOJHw7eGSq3cbNdBHWg4scGH5rPodcjIkeFiDhcVVncjPyWyfVT0WWiOrWYy+qJE2GLbPcb2m+oWj7DfRH\/Ad3JvujqsKqZneFhKk4igzvTd4RHUsPorbFSMkSLlSbioPcVdO5XRvsXS7pQ3++W9Kn\/xRWzksPiqV3I70N2sTciAACopK2vBDqQKlokKp1kZI9JsTUdTdyKKEqQpQai1Sp6U1EgSCpfst4o+51FUqIW2JJQDRHT\/iOQ3H6J1XtJqdd7SL\/wBNie+narbSerZmEamH2lR2+rK36LRvY3bik9O1096pcysBgiyrMq7+oWNkxNx2hO5Ppw2bIbR2oBPQnFjKW0jKnpTmNsmeq1AUn9U2RzUNsGkYWjLcmsDXTImQREGTbcpDczJLSCP33FNnF8+wRO902rRy3K0FlYnrAtlUxB0mjpjrCawNdMiZBEQZNtykNzMktII\/fcU2cXz7BE73TatEYg3O7\/4Q0Qa6stptaRxKd3UGguPc6RCmJdIvaeDxcp0jamFzoEYbb+9N9poyW0wBSbHZ\/wAoDTNrPs25VW2po0gqqZDhnITrC9sjE23Fu6F+qqw0Gae3MXLDMkzLbY7VxCZZTLYIiLQnzm7knyNqq09Iyt1t7bfULR9hvoj\/AIBsmULIj3ZatoeCuK2B3lSUD0LBx+ywcfssPFQbB0DXF4VoKsBU5dCiI9zDx1h3Qoo4qRetoeCsBVrVVEKIlTEasPHVDrVcVsDvKkqzwVoKsCkoO6ERGoCLtVy6NURKm5Tei2InV0joVxWw3xWHjqw8dcG0K4rYHeVJ\/wARyG4\/RGnRuMGJ2ct7tYJyuVWdypzv1UZgSpKGyW1YSc04kfDt4ZKrdxs9xxI+HbwyVW7jZroI60HFjgw\/NZ9DrkZEjwsQcLiqs7kZ+S2T6qeiy0R1a5FtNo78wiRNhi2xdGtvbb6hM7A9EO\/1\/wCXDcfonVe0mp13tIv\/AE2J76dqttJ6tmYRqYfaVHb6srfotG9jduKT07XT3qlzKwGCLKsyrv6hY2TE3HaE7k+nDZshtHagE9CcWMpbSMqelOY2yZ6rUBSf1TZHNQ2waRhaMtyawNdMiZBEQZNtykNzMktII\/fcU2cXz7BE73TatHLcrQWViesC2VTEHSaOmOsJrA10yJkERBk23KQ3MyS0gj99xTZxfPsETvdNq0RiDc7v\/hDRBrqy2m1pHEp3dQaC49zpEKYl0i9p4PFynSNqYXOgRhtv7032mjJbTAFJsdn\/ACgNM2s+zblVbamjSCqpkOGchOsL2yMTbcW7oX6qrDQZp7cxcsMyTMttjtXEJllMtgiItCfObuSPbd6nW3tt9QmdgeigGxX8Ar+AV\/AK\/gFfwCv4BX8Ar+AV\/AK\/gFfwCv4BX8Ar+AV\/AK\/gFfwCv4BX8Ar+AV\/AK\/gFfwCv4BX8Ar+AV\/AK\/gFfwCv4BX8Ar+AV\/AK\/gFfwCv4BX8Ar+AV\/AK\/gFfwCv4BX8Ar+AV\/AK\/gFfwCv4BX8Ar+AV\/AK\/gFfwCv4BX8Ar+AV\/AK\/gFfwCv4BX8Ar+AX2Cv4BYuA5LFwHJYuA5K08Ar+AV\/AK\/gFi4BX8AsXAcli4Dkr+AV\/AK\/gFi4BWu4BX8Ar+AV\/AK\/gFfwCv4BX8Ar+AV\/BX8Ar+AV\/AK\/gFfwCv4K\/gr+Cv4BX8Ar+AV\/AK\/gFfwCv4BX8Ar+AV\/AK\/gEalIvj6hWvH53K9XoEm65VTbcplXqZV6xTF\/wCQm27lEq9Xq9Nt3KJV6vUkyFFYno\/Ar1er1BKqm25AzPRYr\/RXq9CT6WK116IyH11t7bfULR9lvp\/hjr13q\/X91H+XHuX+7b\/gX6rfcj\/Jcu76tQim4dH1TRNkGVYdmLk5pNnJSDG1uV+zUmz4zKEmTPTKBOSmdyt+Uyi6RFqvy6foj0ZW\/ROt4wjnS6UXSItV+XT9EejK36J48O5VSImb0OImFfAtz+i2TBgd6NLrZ6clsGNrcjTZBkIZHqPUuqOnPcr+P0Ts4Mpu5dw1jtN9QtH2W+n+HuR70dQ3Leghu1Hv9wIbke9HUNRQ9w96KCG5BD3Ah3I96KlFd6C8UU2ddmqEP7MqOnW7UNy8Uf8AIKAN0H6I06JxgxMjLe5XK5XXqIVMW35q5UU2gSpIQ2C2rCTnxT7DsX7r+lVFvRxsVyuVyfYdi\/df0qot6ONiuVypi2JQcdG4MPzfwVcrlIbcSPCxBwFh3qITrIpv3KYustkdSuVyNIs77QbUXUmwxFsz3owKSLCDrHab6haPst9P8Uq5DdrG5SpyX0R1BDcj3oq5BRF6KAPf7h70Vco6kECupHWDC8V1oDxRsUdaFi6kUMzqj37UV367lbZ7hHSiM8kDephGf8gobj9E6r2k1Ou9pF\/6bE99O1W2k9WzMI1MPtKjt9WVv0Wjexu3FJ6drp71S5lYDBFlWZV39QsbJibjtCdyfThs2Q2jtQCehOLGUtpGVPSnMbZM9VqApP6psjmobYNIwtGW5NYGumRMgiIMm25SG5mSWkEfvuKbOL59gid7ptWjluVoLKxPWBbKpiDpNHTHWE1ga6ZEyCIgybblIbmZJaQR++4ps4vn2CJ3um1aIxBud3\/whog11ZbTa0jiU7uoNBce50iFMS6Re08Hi5TpG1MLnQIw2396b7TRktpgCk2Oz\/lAaZtZ9m3Kq21NGkFVTIcM5CdYXtkYm24t3Qv1VWGgzT25i5YZkmZbbHauITLKZbBERaE4xdpKu6xPfEAmybLh16x2m+oWj7LfT\/h9in\/OKG4\/RGnRuMGJ2ct7tYJyuVWdypzv1UZgSpKGyW1YSc04kfDt4ZKrdxs9xxI+HbwyVW7jZroI60HFjgw\/NZ9DrkZEjwsQcLiqs7kZ+S2T6qeiy0R1a5FtNo78wi62wxEWz3q6CDBB+2sdpvqFo+y30VQhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhZLJZK8LJXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhZK8K8K8K8K8K8LJZLJXhXhZLJXhZLJZLJZLJZLJZK8LE387le3jyV7fzuVhH53K9vHkr28eSvbx5K9vHkr28eSvb+dyvCyWSyVhH53LE3jyV7fzuWSyWSyWSyWSyWSyWSyWSyWSyWSyWSyWSyWSyWSyWSyVqG4\/ROq9pNTrvaRf+mxPfTtVtpPVszCNTD7So7fVlb9Fo3sbtxSena6e9UuZWAwRZVmVd\/ULGyYm47Qncn04bNkNo7UAnoTixlLaRlT0pzG2TPVagKT+qbI5qG2DSMLRluTWBrpkTIIiDJtuUhuZklpBH77imzi+fYIne6bVo5blaCysT1gWyqYg6TR0x1hNYGumRMgiIMm25SG5mSWkEfvuKbOL59gid7ptWiMQbnd\/wDCGiDXVltNrSOJTu6g0Fx7nSIUxLpF7TweLlOkbUwudAjDbf3pvtNGS2mAKTY7P+UBpm1n2bcqrbU0aQVVMhwzkJ1he2Ribbi3dC\/VVYaDNPbmLlhmSZltsdq4hMsplsERFoTjF2kq7rE98QCbJsuHXrHab6haPst9FEjP1WIfnesQ\/O9Yh+d6xD871iH53rEPzvWIfnesQ\/O9Yh+d6xD871iH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnerx+d6vH53q8fnesbfEc1jb4jmsbfEc18RviOaxt8RzWNviOaxt8RzVhH536rx+d6vH53q1w\/O9Y2+I5rG3xHNXj871ePzvV4\/O9Xj871ePzvV4\/O9Xj871ePzvV4\/O9Xj871ePzvV4\/O9Xj871ePzvV4\/O9Xj871ePzvV4\/O9Xj871ePzvV4\/O9Xj871ePzvV4\/O9Xj871ePzvV4\/O9Xj879Q3H6I06NxgxOzlvdrBOVyqzuVOd+qjMCVJQ2S2rCTmnEj4dvDJVbuNnuOJHw7eGSq3cbNdBHWg4scGH5rPodcjIkeFiDhcVVncjPyWyfVT0WWiOrXItptHfmEXW2GIi2e9XQQYIP21jtN9QtH2W+n\/DNpSP9CUNx+idV7SanXe0i\/8ATYnvp2q20nq2ZhGph9pUdvqyt+i0b2N24pPTtdPeqXMrAYIsqzKu\/qFjZMTcdoTuT6cNmyG0dqAT0JxYyltIyp6U5jbJnqtQFJ\/VNkc1DbBpGFoy3JrA10yJkERBk23KQ3MyS0gj99xTZxfPsETvdNq0ctytBZWJ6wLZVMQdJo6Y6wmsDXTImQREGTbcpDczJLSCP33FNnF8+wRO902rRGINzu\/+ENEGurLabWkcSnd1BoLj3OkQpiXSL2ng8XKdI2phc6BGG2\/vTfaaMltMAUmx2f8AKA0zaz7NuVVtqaNIKqmQ4ZyE6wvbIxNtxbuhfqqsNBmntzFywzJMy22O1cQmWUy2CIi0Jxi7SVd1ie+IBNk2XDr1jtN9QtH2W+n\/AAOz3IKsu9yNc\/55Q3H6I06NxgxOzlvdrBOVyqzuVOd+qjMCVJQ2S2rCTmnEj4dvDJVbuNnuOJHw7eGSq3cbNdBHWg4scGH5rPodcjIkeFiDhcVVncjPyWyfVT0WWiOrXItptHfmEXWwLLjPheg4XHWO031C0fZb6I\/8AsVvu1DVCkqFJ9zZVqs\/zChuP0TqvaTU672kX\/psT307VbaT1bMwjUw+0qO31ZW\/RaN7G7cUnp2unvVLmVgMEWVZlXf1CxsmJuO0J3J9OGzZDaO1AJ6E4sZS2kZU9Kcxtkz1WoCk\/qmyOahtg0jC0Zbk1ga6ZEyCIgybblIbmZJaQR++4ps4vn2CJ3um1aOW5WgsrE9YFsqmIOk0dMdYTWBrpkTIIiDJtuUhuZklpBH77imzi+fYIne6bVojEG53f\/CGiDXVltNrSOJTu6g0Fx7nSIUxLpF7TweLlOkbUwudAjDbf3pvtNGS2mAKTY7P+UBpm1n2bcqrbU0aQVVMhwzkJ1he2Ribbi3dC\/VVYaDNPbmLlhmSZltsdq4hMsplsERFoT+1yQnpPqdY7TfULR9lvp\/v7f7VqgKf9GUNx+iNOjcYMTs5b3awTlcqs7lTnfqozAlSUNktqwk5pxI+HbwyVW7jZ7jiR8O3hkqt3GzXQR1oOLHBh+az6HXIyJHhYg4XFVZ3Iz8lsn1U9Flojq1yLabR35hF1sCy4z4XoOFx1jtN9QtH2W+n\/ALP7Vv+kKG4\/ROq9pNTrvaRf+mxPfTtVtpPVszCNTD7So7fVlb9Fo3sbtxSena6e9UuZWAwRZVmVd\/ULGyYm47Qncn04bNkNo7UAnoTixlLaRlT0pzG2TPVagKT+qbI5qG2DSMLRluTWBrpkTIIiDJtuUhuZklpBH77imzi+fYIne6bVo5blaCysT1gWyqYg6TR0x1hNYGumRMgiIMm25SG5mSWkEfvuKbOL59gid7ptWiMQbnd\/wDCGiDXVltNrSOJTu6g0Fx7nSIUxLpF7TweLlOkbUwudAjDbf3pvtNGS2mAKTY7P+UBpm1n2bcqrbU0aQVVMhwzkJ1he2Ribbi3dC\/VVYaDNPbmLlhmSZltsdq4hMsplsERFoT+1yQnpPqdY7TfULR9lvp\/v7f7c\/27Fb\/kFDcfojTo3GDE7OW92sE5XKrO5U536qMwJUlDZLasJOacSPh28MlVu42e44kfDt4ZKrdxs10EdaDixwYfms+h1yMiR4WIOFxVWdyM\/JbJ9VPRZaI6tci2m0d+YRdbAsuM+F6DhcdY7TfULR9lvp\/gXf2IUarv9Fb7t6sXSuvXf\/as\/t3\/AOCUNx+idV7SanXe0i\/9Nie+narbSerZmEamH2lR2+rK36LRvY3bik9O1096pcysBgiyrMq7+oWNkxNx2hO5Ppw2bIbR2oBPQnFjKW0jKnpTmNsmeq1AUn9U2RzUNsGkYWjLcmsDXTImQREGTbcpDczJLSCP33FNnF8+wRO902rRy3K0FlYnrAtlUxB0mjpjrCawNdMiZBEQZNtykNzMktII\/fcU2cXz7BE73TatEYg3O7\/4Q0Qa6stptaRxKd3UGguPc6RCmJdIvaeDxcp0jamFzoEYbb+9N9poyW0wBSbHZ\/ygNM2s+zblVbamjSCqpkOGchOsL2yMTbcW7oX6qrDQZp7cxcsMyTMttjtXEJllMtgiItCf2uSE9J9TrHab6haPst9P79qsUNUFQEdXVr3KNUKz88FFytIOsLEP7MapnXv9yxW+5Kv14VPTrsTh8yMixSJs1FwUwB1rqVl6Lpu1E3kLEAhm73hKjLVZ\/jFDcfojTo3GDE7OW92sE5XKrO5U536qMwJUlDZLasJOacSPh28MlVu42e44kfDt4ZKrdxs10EdaDixwYfms+h1yMiR4WIOFxVWdyM\/JbJ9VPRZaI6tci2m0d+YRdbYYiLZ3FXQReD9tY7TfULR9lvp\/g2KTfqvUzb7nWplTK69V6k6r1erf7dmqZXVqnVYVfrvVly69UK\/3LCjVmiJEHXMrpVJVyjpREzKi5WldatOuNUlX67f8Uobj9E6r2k1Ou9pF\/wCmxPfTtVtpPVszCNTD7So7fVlb9Fo3sbtxSena6e9UuZWAwRZVmVd\/ULGyYm47Qncn04bNkNo7UAnoTixlLaRlT0pzG2TPVagKT+qbI5qG2DSMLRluTWBrpkTIIiDJtuUhuZklpBH77imzi+fYIne6bVo5blaCysT1gWyqYg6TR0x1hNYGumRMgiIMm25SG5mSWkEfvuKbOL59gid7ptWiMQbnd\/8ACGiDXVltNrSOJTu6g0Fx7nSIUxLpF7TweLlOkbUwudAjDbf3pvtNGS2mAKTY7P8AlAaZtZ9m3Kq21NGkFVTIcM5CdYXtkYm24t3Qv1VWGgzT25i5YZkmZbbHauITLKZbBERaFpDSSarB4Im0uJlxILfXWO031C0fZb6f4sf7C\/3o\/v2f4hQ3H6I06NxgxOzlvdrBOVyqzuVOd+qjMCVJQ2S2rCTmnEj4dvDJVbuNnuOJHw7eGSq3cbNdBHWg4scGH5rPodcjIkeFiDhcVVncjPyWyfVT0WWiOrXItptHfmEXW2GIi2dxV0EXg\/bWO031C0fZb6f4NylExcpixEhWqVco9wHUOjWMlGq5QBqgBQoUEe9bqiFYrvd69Vuuz3urUJQ6M9caohR7t+vo9wR\/ijcfonVe0mp13tIv\/TYnvp2q20nq2ZhGph9pUdvqyt+i0b2N24pPTtdPeqXMrAYIsqzKu\/qFjZMTcdoTuT6cNmyG0dqAT0JxYyltIyp6U5jbJnqtQFJ\/VNkc1DbBpGFoy3JrA10yJkERBk23KQ3MyS0gj99xTZxfPsETvdNq0ctytBZWJ6wLZVMQdJo6Y6wmsDXTImQREGTbcpDczJLSCP33FNnF8+wRO902rRGINzu\/+ENEGurLabWkcSnd1BoLj3OkQpiXSL2ng8XKdI2phc6BGG2\/vTfaaMltMAUmx2f8oDTNrPs25VW2po0gqqZDhnITrC9sjE23Fu6F+qqw0Gae3MXLDMkzLbY7VxCZZTLYIiLQtIaSTVYPBE2lxMuJBb66x2m+oWj7LfT\/AAIUKlUyqVfriVeplEnNG2zJQrFZqnXItVRyUwphTCmFEd+q7XCt1yrvekq33OpSSonXYpjVN6sVtypbqmFMe9YUJJPviLf8Qbj9EadG4wYnZy3u1gnK5VZ3KnO\/VRmBKkobJbVhJzTiR8O3hkqt3Gz3HEj4dvDJVbuNmugjrQcWODD81n0OuRkSPCxBwuKqzuRn5LZPqp6LLRHVrkW02jvzCLrbDERbO4q6CLwftrHab6haPst9P9XZ\/qoW1qj\/ABihuP0TqvaTU672kX\/psT307VbaT1bMwjUw+0qO31ZW\/RaN7G7cUnp2unvVLmVgMEWVZlXf1CxsmJuO0J3J9OGzZDaO1AJ6E4sZS2kZU9Kcxtkz1WoCk\/qmyOahtg0jC0Zbk1ga6ZEyCIgybblIbmZJaQR++4ps4vn2CJ3um1aOW5WgsrE9YFsqmIOk0dMdYTWBrpkTIIiDJtuUhuZklpBH77imzi+fYIne6bVojEG53f8Awhog11ZbTa0jiU7uoNBce50iFMS6Re08Hi5TpG1MLnQIw2396b7TRktpgCk2Oz\/lAaZtZ9m3Kq21NGkFVTIcM5CdYXtkYm24t3Qv1VWGgzT25i5YZkmZbbHauITLKZbBERaFpDSSarB4Im0uJlxILfXWO031C0fZb6f6G\/3NkqTqvVp9yz+91K9RGq7VYjqt1SFP96f8kobj9EadG4wYnZy3u1gnK5VZ3KnO\/VRmBKkobJbVhJzTiR8O3hkqt3Gz3HEj4dvDJVbuNmugjrQcWODD81n0OuRkSPCxBwuKqzuRn5LZPqp6LLRHVrkW02jvzCLgCYMRFvFXQQYIP21jtN9QtH2W+nu367\/ct\/yZyUi5QdU6oUKPdOrZVqKhEqVKt9y02a56VcoKpy6UMvej\/ROQ3H6J1XtJqdd7SL\/02J76dqttJ6tmYRqYfaVHb6srfotG9jduKT07XT3qlzKwGCLKsyrv6hY2TE3HaE7k+nDZshtHagE9CcWMpbSMqelOY2yZ6rUBSf1TZHNQ2waRhaMtyawNdMiZBEQZNtykNzMktII\/fcU2cXz7BE73TatHLcrQWViesC2VTEHSaOmOsJrA10yJkERBk23KQ3MyS0gj99xTZxfPsETvdNq0RiDc7v8A4Q0Qa6stptaRxKd3UGguPc6RCmJdIvaeDxcp0jamFzoEYbb+9N9poyW0wBSbHZ\/ygNM2s+zblVbamjSCqpkOGchOsL2yMTbcW7oX6qrDQZp7cxcsMyTMttjtXEJllMtgiItCeSDiy7k6AaSdmq\/rvt1jtN9QtH2W+n+qv12qxX\/4lylWn\/Fs\/wARyG4\/RGnRuMGJ2ct7tYJyuVWdypzv1UZgSpKGyW1YSc04kfDt4ZKrdxs9xxI+HbwyVW7jZroI60HFjgw\/NZ9DrkZEjwsQcLiqs7kZ+S2T6qeiy0R1a5FtNo78wi4AmDERbxV0EGCD9tY7TfULR9lvoiB\/ohtX+9JKg67VI9+9Qo9yB7s\/2SP7Fn9i3\/FchuP0TqvaTU672kX\/AKbE99O1W2k9WzMI1MPtKjt9WVv0Wjexu3FJ6drp71S5lYDBFlWZV39QsbJibjtCdyfThs2Q2jtQCehOLGUtpGVPSnMbZM9VqApP6psjmobYNIwtGW5NYGumRMgiIMm25SG5mSWkEfvuKbOL59gid7ptWjluVoLKxPWBbKpiDpNHTHWE1ga6ZEyCIgybblIbmZJaQR++4ps4vn2CJ3um1aIxBud3\/wAIaINdWW02tI4lO7qDQXHudIhTEukXtPB4uU6RtTC50CMNt\/em+00ZLaYApNjs\/wCUBpm1n2bcqrbU0aQVVMhwzkJ1he2Ribbi3dC\/VVYaDNPbmLlhmSZltsdq4hMsplsERFoTyQcWXcnQDSTs1X9d9usdpvqFo+y30R\/0MLEZyXWrgXbk0LYs3goTZNliAmlAnVDGravQb02KGtlEgUlQRHWjNo1HVKJ1woUST7ohWFWFAKJWyIGStt71nK6j\/pX9yG4\/RGnRuMGJ2ct7tYJyuVWdypzv1UZgSpKGyW1YSc04kfDt4ZKrdxs9xxI+HbwyVW7jZroI60HFjgw\/NZ9DrkZEjwsQcLiqs7kZ+S2T6qeiy0R1a5FtNo78wi4AmDERbxV0EGCD9tY7TfULR9lvoj\/orbYW5ZargrVaAVbq2XWqSVestcC3VH9i33IHu26pU3lSp\/0j+5DcfonVe0mp13tIv\/TYnvp2q20nq2ZhGph9pUdvqyt+i0b2N24pPTtdPeqXMrAYIsqzKu\/qFjZMTcdoTuT6cNmyG0dqAT0JxYyltIyp6U5jbJnqtQFJ\/VNkc1DbBpGFoy3JrA10yJkERBk23KQ3MyS0gj99xTZxfPsETvdNq0ctytBZWJ6wLZVMQdJo6Y6wmsDXTImQREGTbcpDczJLSCP33FNnF8+wRO902rRGINzu\/wDhDRBrqy2m1pHEp3dQaC49zpEKYl0i9p4PFynSNqYXOgRhtv7032mjJbTAFJsdn\/KA0zaz7NuVVtqaNIKqmQ4ZyE6wvbIxNtxbuhfqqsNBmntzFywzJMy22O1cQmWUy2CIi0J5IOLLuToBpJ2ar+u+3WO031C0fZb6I\/2blb7gKuUH3LP7sBW+5NnuW+5VcFULR1f2bESbPet9+xdCsXTGqbFdZquUmxSXapyU3+7BU65U\/wCA7uQ3H6I06NxgxOzlvdrBOVyqzuVOd+qjMCVJQ2S2rCTmnEj4dvDJVbuNnuOJHw7eGSq3cbNdBHWg4scGH5rPodcjIkeFiDhcVVncjPyWyfVT0WWiOrXItptHfmESMrLbFU26Y8NY7TfULR9lvoj\/AGLVFy\/qZqwSTdKm46oKvJ6oUusCgxHer5Cig9yuu8dVuqVJsU5\/nQpsnVJtVl62r0Kbl1poUWR3rZVq61dUrGkb0E3VUbepWMcFJgDrVLdorbVQtCqPgr1YoN+q29U0kjpCskdStsWw65V6TNSEaYDR0q0yepHLqVxUuz13aryEQj1LaUhDrCsVJaY6VYTuUdyts32KyKBcSrLSp6FIUqwq7VN6uKyUC3VM26jrs\/sv7kNx+idV7SanXe0i\/wDTYnvp2q20nq2ZhGph9pUdvqyt+i0b2N24pPTtdPeqXMrAYIsqzKu\/qFjZMTcdoTuT6cNmyG0dqAT0JxYyltIyp6U5jbJnqtQFJ\/VNkc1DbBpGFoy3JrA10yJkERBk23KQ3MyS0gj99xTZxfPsETvdNq0ctytBZWJ6wLZVMQdJo6Y6wmsDXTImQREGTbcpDczJLSCP33FNnF8+wRO902rRGINzu\/8AhDRBrqy2m1pHEp3dQaC49zpEKYl0i9p4PFynSNqYXOgRhtv7032mjJbTAFJsdn\/KA0zaz7NuVVtqaNIKqmQ4ZyE6wvbIxNtxbuhfqqsNBmntzFywzJMy22O1cQmWUy2CIi0J4wy\/MTIs3I1\/+RyjPWO031C0fZb6I\/2JCuK2rlTTZriFmFOSuKJc29GB3Inp9yFYocoA1Q4LZ8StptqkXLD3qY1SrbVBbxWGCoF2oHVa1Q0LDO9S0EdWS2gohQRYrB4qGiD4q5WWK5XWoxaT0qlwkKxpUOCsaop7lMQepWtVgW2FYFdZqt1VEwtmR0qMxkqZt6lQJQE2hZg+Kpb4lRAhUxI6ygKZ6lIEFVzEolyilQ1sLrVlpV2q6VYCrdZPux77+5DcfojTo3GDE7OW92sE5XKrO5U536qMwJUlDZLasJOacSPh28MlVu42e44kfDt4ZKrdxs10EdaDixwYfms+h1yMiR4WIOFxVWdyM\/JbJ9VPRZaI6tci2m0d+YRIystsVTbpjw1jtN9QtH2W+iP\/ABGzXYVaf7FnuWe5apCt95\/chuP0TqvaTU672kX\/AKbE99O1W2k9WzMI1MPtKjt9WVv0Wjexu3FJ6drp71S5lYDBFlWZV39QsbJibjtCdyfThs2Q2jtQCehOLGUtpGVPSnMbZM9VqApP6psjmobYNIwtGW5NYGumRMgiIMm25SG5mSWkEfvuKbOL59gid7ptWjluVoLKxPWBbKpiDpNHTHWE1ga6ZEyCIgybblIbmZJaQR++4ps4vn2CJ3um1aIxBud3\/wAIaINdWW02tI4lO7qDQXHudIhTEukXtPB4uU6RtTC50CMNt\/em+00ZLaYApNjs\/wCUBpm1n2bcqrbU0aQVVMhwzkJ1he2Ribbi3dC\/VVYaDNPbmLlhmSZltsdq4hMsplsERFoTxhl+YmRZuRr\/API5RnrHab6haPst9E7\/AJHarP7LkNx+iNOjcYMTs5b3awTlcqs7lTnfqozAlSUNktqwk5pxI+HbwyVW7jZ7jiR8O3hkqt3GzXQR1oOLHBh+az6HXIyJHhYg4XFVZ3Iz8lsn1U9Flojq1yLabR35hEjKy2xVNumPDWO031C0fZb6J3+PP\/Fn9yG4\/ROq9pNTrvaRf+mxPfTtVtpPVszCNTD7So7fVlb9Fo3sbtxSena6e9UuZWAwRZVmVd\/ULGyYm47Qncn04bNkNo7UAnoTixlLaRlT0pzG2TPVagKT+qbI5qG2DSMLRluTWBrpkTIIiDJtuUhuZklpBH77imzi+fYIne6bVo5blaCysT1gWyqYg6TR0x1hNYGumRMgiIMm25SG5mSWkEfvuKbOL59gid7ptWiMQbnd\/wDCGiDXVltNrSOJTu6g0Fx7nSIUxLpF7TweLlOkbUwudAjDbf3pvtNGS2mAKTY7P+UBpm1n2bcqrbU0aQVVMhwzkJ1he2Ribbi3dC\/VVYaDNPbmLlhmSZltsdq4hMsplsERFoTxhl+YmRZuRr\/8jlGesdpvqFo+y30Tv9Jf\/wAJf3Ibj9EadG4wYnZy3u1gnK5VZ3KnO\/VRmBKkobJbVhJzTiR8O3hkqt3Gz3HEj4dvDJVbuNmugjrQcWODD81n0OuRkSPCxBwuKqzuRn5LZPqp6LLRHVrkW02jvzCLgDYYi48Va0t3x9CdY7TfULR9lvonf6i1Xa7Ndn+2v\/sP7vqhuP0TqvaTU672kX\/psT307VbaT1bMwjUw+0qO31ZW\/RaN7G7cUnp2unvVLmVgMEWVZlXf1CxsmJuO0J3J9OGzZDaO1AJ6E4sZS2kZU9Kcxtkz1WoCk\/qmyOahtg0jC0Zbk1ga6ZEyCIgybblIbmZJaQR++4ps4vn2CJ3um1aOW5WgsrE9YFsqmIOk0dMdYTWBrpkTIIiDJtuUhuZklpBH77imzi+fYIne6bVojEG53f8Awhog11ZbTa0jiU7uoNBce50iFMS6Re08Hi5TpG1MLnQIw2396b7TRktpgCk2Oz\/lAaZtZ9m3Kq21NGkFVTIcM5CdYXtkYm24t3Qv1VWGgzT25i5YZkmZbbHauITLKZbBERaFpKg6K52b8kb6J2apnjbrHab6haPst9Ef9PZ\/wbSd31Q3H6I06NxgxOzlvdrBOVyqzuVOd+qjMCVJQ2S2rCTmnEj4dvDJVbuNnuOJHw7eGSq3cbNdBHWg4scGH5rPodcjIkeFiDhcVVncjPyWyfVT0WWiOrXItptHfmEXAGwxFx4q1pbvj6E6x2m+oWj7LfRO\/wCW6Tu+qG4\/ROq9pNTrvaRf+mxPfTtVtpPVszCNTD7So7fVlb9Fo3sbtxSena6e9UuZWAwRZVmVd\/ULGyYm47Qncn04bNkNo7UAnoTixlLaRlT0pzG2TPVagKT+qbI5qG2DSMLRluTWBrpkTIIiDJtuUhuZklpBH77imzi+fYIne6bVo5blaCysT1gWyqYg6TR0x1hNYGumRMgiIMm25SG5mSWkEfvuKbOL59gid7ptWiMQbnd\/8IaINdWW02tI4lO7qDQXHudIhTEukXtPB4uU6RtTC50CMNt\/em+00ZLaYApNjs\/5QGmbWfZtyqttTRpBVUyHDOQnWF7ZGJtuLd0L9VVhoM09uYuWGZJmW2x2riEyymWwREWhaSoOiudm\/JG+idmqZ426x2m+oWj7LfRO\/wCW6Tu+qG4\/RGnRuMGJ2ct7tYJyuVWdypzv1UZgSpKGyW1YSc04kfDt4ZKrdxs9xxI+HbwyVW7jZroI60HFjgw\/NZ9DrkZEjwsQcLiqs7kZ+S2T6qeiy0R1a5FtNo78wi4A2GIuPFWtLd8fQnWO031C0fZb6KpzZJnMrBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJWDiVg4nmsHE81h4nmsPE81h4lYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYOJ5rBxPNYeJWHieawcTzWDieawcTzWDieawcTzWDieawcTzWDieawcTzWDieawcTzWDieawcTzWDieawcTzWDieawcTzWDieawcTzWDieawcTzWDieawcTzWDieawcTzWDiea\/piJv\/AAobj9E6r2k1Ou9pF\/6bE99O1W2k9WzMI1MPtKjt9WVv0Wjexu3FJ6drp71S5lYDBFlWZV39QsbJibjtCdyfThs2Q2jtQCehOLGUtpGVPSnMbZM9VqApP6psjmobYNIwtGW5NYGumRMgiIMm25SG5mSWkEfvuKbOL59gid7ptWjluVoLKxPWBbKpiDpNHTHWE1ga6ZEyCIgybblIbmZJaQR++4ps4vn2CJ3um1aIxBud3\/whog11ZbTa0jiU7uoNBce50iFMS6Re08Hi5TpG1MLnQIw2396b7TRktpgCk2Oz\/lAaZtZ9m3Kq21NGkFVTIcM5CdYXtkYm24t3Qv1VWGgzT25i5YZkmZbbHauITLKZbBERaFpKg6K52b8kb6J2apnjbrHab6haPst9FRRMZz9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9l8Pj9k7Zpp6\/sEXxMZL4fH7L4fH7L4fH\/wCV8Lj\/APK+Hx\/+V8Pj\/wDK+Hx\/+V8Pj\/8AK+Hx\/wDlfD4\/ZfD4\/ZfD4\/8Ayvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsvh8fsnbNNP17kNx+iNOjcYMTs5b3awTlcqs7lTnfqozAlSUNktqwk5pxI+HbwyVW7jZ7jiR8O3hkqt3GzXQR1oOLHBh+az6HXIyJHhYg4XFVZ3Iz8lsn1U9Flojq1yLabR35hF9sAxdb4K4t6j9tY7TfULR9lvonf37BKwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+CwnwWE+Gqxp8FhPgsJ8FhPgsJ8FhPgsJ8FhPgsJ8Pc0nd9U7u9f9LpO76obj9E6r2k1Ou9pF\/wCmxPfTtVtpPVszCNTD7So7fVlb9Fo3sbtxSena6e9UuZWAwRZVmVd\/ULGyYm47Qncn04bNkNo7UAnoTixlLaRlT0pzG2TPVagKT+qbI5qG2DSMLRluTWBrpkTIIiDJtuUhuZklpBH77imzi+fYIne6bVo5blaCysT1gWyqYg6TR0x1hNYGumRMgiIMm25SG5mSWkEfvuKbOL59gid7ptWiMQbnd\/8ACGiDXVltNrSOJTu6g0Fx7nSIUxLpF7TweLlOkbUwudAjDbf3pvtNGS2mAKTY7P8AlAaZtZ9m3Kq21NGkFVTIcM5CdYXtkYm24t3Qv1VWGgzT25i5YZkmZbbHauITLKZbBERaE+\/4lVnQIuRvonZqmf8A2t1jtN9QtH2W+icnbVNPVKDa8QkGPohog+03y2EB7XpnZu4oNGkqe6IEdKdTpKnMtIhfEtibrPFDSF5g24Z9CqG+KpZpKnzFMQnUvqcy1zYUDSbREgQvaF5A7P3VLLStIDeP9W7eUNW733bzr0nd9U7u9dbTogGaRljh9U3\/APH0DZc2wuzJRgtc5t7QbUNN8pMJjjc8SFpXUB7xdIlF+k0Y0TwdmBTwTWaIAbO5F4c14bfSZTZtrtbCOjskWk5BVyHN6WmUCS0F1zSbSoaIEBe1ymnrXttMTSTADb+KaWmWPEt91rDZK9m\/RlguGln8CI0YqhQ8QqqLPzL+zpO76obj9EadG4wYnZy3u1gnK5VZ3KnO\/VRmBKkobJbVhJzTiR8O3hkqt3Gz3HEj4dvDJVbuNmugjrQcWODD81n0OuRkSPCxBwuKqzuRn5LZPqp6LLRHVrkW02jvzCL7YBi63wVxb1H7ax2m+oWj7LfROTg6dq6F7QOLqhBAHNDSGsuG7mho7TbiIt9UzSMnZsg9Se5lVWk6ckWucWgiLuNiDHVRcYgSq24eMIO0bTNVVvotI5tVWk6ckHta4vaIbNyDX1fqiLVbV7PqvWki7L\/Vu3lDUdVyugdHuO3nXpO76p3d66yXGkU5oPddn3p2nOla5tsAXmepezraxwdO1YtCGOqpC0u1Dsulbbi7fam1kEUWdE9a0ujdpGSRstZh8V\/U\/wD8DPctJ7SP6n\/ld3otnRW\/Lowm6YaQNEbQN43BS0yICrq2pij6poYKix1oC0OjOJot90B5hvStt1X\/AOP\/AOTouWl0bBVLpDJpkLRNewMAdNNVRhe2Y6NHeNJNwWk0gcCGm09KY+yH3Kh1\/V72k7vqhuP0TqvaTU672kX\/AKbE99O1W2k9WzMI1MPtKjt9WVv0Wjexu3FJ6drp71S5lYDBFlWZV39QsbJibjtCdyfThs2Q2jtQCehOLGUtpGVPSnMbZM9VqApP6psjmobYNIwtGW5NYGumRMgiIMm25SG5mSWkEfvuKbOL59gid7ptWjluVoLKxPWBbKpiDpNHTHWE1ga6ZEyCIgybblIbmZJaQR++4ps4vn2CJ3um1aIxBud3\/wAIaINdWW02tI4lO7qDQXHudIhTEukXtPB4uU6RtTC50CMNt\/em+00ZLaYApNjs\/wCUBpm1n2bcqrbU0aQVVMhwzkJ1he2Ribbi3dC\/VVYaDNPbmLlhmSZltsdq4hMsplsERFoT7\/iVWdAi5G+idmqZ\/wDa3WO031C0fZb6J398luay8Fl4LLwWXgsvBZeCy8Fl4LLwWXgsvBZeCy8Fl4LLwWXgsvBZeCy8Fl4LLwWXgsvBZeCy8Fl4LLwWXgsvBZeCy8Fl4LLwWXgsvBZeCy8Fl4LLwWXgsvBZeCk5oAtlYOKwcVg4rBxWDisHFYOKwcUT069J3fVO7vX+57MNa1pvpET72ySNyk2n\/C0nd9UNx+iNOjcYMTs5b3awTlcqs7lTnfqozAlSUNktqwk5pxI+HbwyVW7jZ7jiR8O3hkqt3GzXQR1oOLHBh+az6HXIyJHhYg4XFVZ3Iz8lsn1U9Flojq1yLabR35hF9sAxdb4K4t6j9tY7TfULR9lvoi5rZB6wsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmsHEc1g4jmn+0ETEfgTmstNiwcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOawcRzWDiOafWImIQ3H6J1XtJqdd7SL\/ANNie+narbSerZmEamH2lR2+rK36LRvY3bik9O1096pcysBgiyrMq7+oWNkxNx2hO5Ppw2bIbR2oBPQnFjKW0jKnpTmNsmeq1AUn9U2RzUNsGkYWjLcmsDXTImQREGTbcpDczJLSCP33FNnF8+wRO902rRy3K0FlYnrAtlUxB0mjpjrCawNdMiZBEQZNtykNzMktII\/fcU2cXz7BE73TatEYg3O7\/wCENEGurLabWkcSnd1BoLj3OkQpiXSL2ng8XKdI2phc6BGG2\/vTfaaMltMAUmx2f8oDTNrPs25VW2po0gqqZDhnITrC9sjE23Fu6F+qqw0Gae3MXLDMkzLbY7VxCZZTLYIiLQn3\/Eqs6BFyN9E7NUz\/AO1usdpvqFo+y30Q7\/X\/AJcNx+iNOjcYMTs5b3awTlcqs7lTnfqozAlSUNktqwk5pxI+HbwyVW7jZ7jiR8O3hkqt3GzXQR1oOLHBh+az6HXIyJHhYg4XFVZ3Iz8lsn1U9Flojq1yLabR35hEtaTBiLj\/AO0IiktLemM9xOsdpvqFo+y30VQvOq1WDVartVyuVyuVrVcrlYFgKsCuWErCVcVcrld\/duKuKuVyu\/4JpO76obj9E6r2k1Ou9pF\/6bE99O1W2k9WzMI1MPtKjt9WVv0Wjexu3FJ6drp71S5lYDBFlWZV39QsbJibjtCdyfThs2Q2jtQCehOLGUtpGVPSnMbZM9VqApP6psjmobYNIwtGW5NYGumRMgiIMm25SG5mSWkEfvuKbOL59gid7ptWjluVoLKxPWBbKpiDpNHTHWE1ga6ZEyCIgybblIbmZJaQR++4ps4vn2CJ3um1aIxBud3\/AMIaINdWW02tI4lO7qDQXHudIhTEukXtPB4uU6RtTC50CMNt\/em+00ZLaYApNjs\/5QGmbWfZtyqttTRpBVUyHDOQnWF7ZGJtuLd0L9VVhoM09uYuWGZJmW2x2riEyymWwREWhaQkfMTwUuxOtdrHab6haPst9EO\/11F3Qq66ZuEI6LSYhmi72l2VIQf7SJ\/SEXHJRpLnXLZNJ6b01ntJq6gtp9Q6IhOqyUtvTX3uMKaxPRCIcIc2\/VZBi\/8AsXSro9y1XQrFZCtKsVkL5dW0FaFdqs\/t2f59v+O\/uQ3H6I06NxgxOzlvdrBOVyqzuVOd+qjMCVJQ2S2rCTmnEj4dvDJVbuNnuOJHw7eGSq3cbNdBHWg4scGH5rPodcjIkeFiDhcVVncjPyWyfVT0WWiOrXItptHfmES1pMGIuP8A7QiKS0t6Yz3E6x2m+oWj7LfRDv8AXV\/UwlTodJZkCvZaTF0hOTfzNDRi9ybSxzaMyEHdK0X5nqfvRTBpLJAX9PSdx\/Cix4hzdRIvdfqnWUdduoa7tZR\/vXlYisRVhPuXK3+5YrrFbrvV6vVh1XlX+5crlcrlYFcrlhVyuVyuV3vYVhWFYVhWFOqzhDcfonVe0mp13tIv\/TYnvp2q20nq2ZhGph9pUdvqyt+i0b2N24pPTtdPeqXMrAYIsqzKu\/qFjZMTcdoTuT6cNmyG0dqAT0JxYyltIyp6U5jbJnqtQFJ\/VNkc1DbBpGFoy3JrA10yJkERBk23KQ3MyS0gj99xTZxfPsETvdNq0ctytBZWJ6wLZVMQdJo6Y6wmsDXTImQREGTbcpDczJLSCP33FNnF8+wRO902rRGINzu\/+ENEGurLabWkcSnd1BoLj3OkQpiXSL2ng8XKdI2phc6BGG2\/vTfaaMltMAUmx2f8oDTNrPs25VW2po0gqqZDhnITrC9sjE23Fu6F+qqw0Gae3MXLDMkzLbY7VxCZZTLYIiLQtISPmJ4KXYnWu1jtN9QtH2W+iHf66qTcVS0tLcpXtNIZei0ZoNN4XtHXZIjpRa6IyTHi5t+ov0RFt4K\/qEBvQ1U+Cplp6Cb0XEy91+p1ZkfL7pR9yEPePuR\/i3rEr1frvV6vV\/vXK5Xa7lcrtV3+UNx+iNOjcYMTs5b3awTlcqs7lTnfqozAlSUNktqwk5pxI+HbwyVW7jZ7jiR8O3hkqt3GzXQR1oOLHBh+az6HXIyJHhYg4XFVZ3Iz8lsn1U9Flojq1yLabR35hEtaTBiLj\/7QiKS0t6Yz3E6x2m+oWj7LfRQf+XAo1EySTY4i87059lRc0tPRESq3Y5Jq6sk2ikPAg9f4VtAOaGhonvUy2sta0u3G3gnAFsOjZaKR15m9FzQA0gCB3pzG2VA8UONqcGwA5lPJBvWOBlViDbf81vXNqa4ubPzQLXbzKY3Zlt8ipp9ERYJZSbc8kG9Y4GVWINt\/zW9c2pri5s\/NAtdvMpkRIxda9lZNNKcam\/pJEuG4yF7Sw3X39zgUS+HNtIHRJQa8NIDYA68ygdLD9gN6bVD7ZbS9FpIcLMUTin0VVTZqmY2o6JnoVWyQZmcVvXmm3YYdHSndZlW4iZcqyBOodpvqEyx2EfKehYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyWF3lPJYXeU8lhd5TyQsdib8p6V\/\/EACwQAQACAQIEBwEBAQEBAQEBAAEAESExQRBRYfBxgaGxwdHxkSDhMEBQYHD\/2gAIAQEAAT8hASBI6hQztj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxO2PidsfE7Y+J2x8Ttj4nbHxMsSwAAEUdG\/Kdl5JUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVGmGFscqPlwHRj+Z9hrtC+0mogf4wXYCFox4A4Fj3uyVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlTSld4JRIi3jdxZZrnvewpaXs165zG7d\/bGj34zNO2k6WdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjnRzo50c6OdHOjlgV0bNkyM8T+R8T+R8T+RZZAHlg1dBymHeYTtXLPV\/8Amcalf4FQ41ElSuA4ipUqVKlSokT\/APeSVAlSpUrh6E+YKgGg53gRRUoK2qdFn5hPzCfmE\/MJmYUEcjYbdJ+YQVYQ0KpU+Ij1OoHTS9fNMNTTpZy0s\/k\/MJ+YT8wn5hFZgseTzJ+YmmumKXmD3iFZL5GHVuFTAq5DygpZ\/BPzCfmE\/MJ+YT8wn5hPzCfmE\/MJ+YT8wn5hAfQn4CZyGDoBwPdq5Z6r\/wBz\/V\/5qVK\/80icH\/8AcqVKlR4+x+Y95zlKkXuUUlQqJNmjP6Fxoybg1fyaqj2TFLiapIo8lJb8HA8srQNLzhR2lqllg38WZWRlrZQbsDb0RPFQ5BQYoczaWIhBa1cdLyjQgGQUnbcjVYlDS2ayg7gt1YiZPKUo1r6F9CLzS8q9LyYtgK52CoRRVmsZgioo4GF1FF5xpQIV8fMKBcZvzl6bRFyDgzMdHXfy8huaF5Ej\/oPmaTwFUw8mq\/8AXbCRZvU+Hfho8n24Hu1cs9d\/7kvtBroe87Kfc7KfcGtfln2iI0lPLh30++NYbZjCPEgUBq6Tup9zvp9wJfpp9orUKTZlZWOUnbnBi8Oi\/REizwB+wjdJ25y+KTvP2na+yd77JnV+X\/T\/ANK\/\/Mr\/ACx4ex+Y95z\/AMcuDnk1fOtJlVzBPQtPf\/wfQy7Sr7wOaCg6ef8A8bTDVce8Q1P6QYVUh4634cD3auWes\/8AS5cuDPh+Z4kN608p3\/iWiXabn9hI+E\/HDaOvD3XtPZe8X8XvN7KX\/J3\/AImtF0dTz0iwdP8ASNxq9MynQyXtvn9CdB8\/ozo5F3qyunBcWGnGIw+B1UdUof2f\/rHCv8PH2vzHvOcgstZfF8V6Jkl7QZwvfL3vBuu0pvCvFv8AEnX+WXJpv9fKLwra28r8C\/1ZhlaBzl1wfWIopY8nwHogLLWXxfFeiZJe0GcL3y97wbrtKbwrxb\/EnX+WXJpv9fKLwra28r8C\/wBWYZWgc5dcH1iKKWPJ8B6ICy1l8XxXomSXtBnC98ve8G67Sm8K8W\/xJ1\/llyab\/Xyi8K2tvK\/Av9WYZWgc5dcH1iKKWPJ8B6JedrL+q\/8AUg6+EPdq5Z6z\/wByd7zny\/EuW5y8cN5tLzw997T23vPSPeese0YKK1oQx20ZfCCx5sMXfN+Ipnq+LKlRmwVvWq9GZTNbe9fI\/wAGkdXgMPrfhUrm5fMBxAinU0fzSbBsVquDtdRh6aTXQi6kGtU9mdCdTXjBgMNSrEIytk7KgW0Qcnp7ESsDyOUvq6rD00mshFDQ2G7NSPmp7w4arZw9YRWZWjy8YI9rUiijxLflhUz3Qor\/AHMwi20weYSwgXgP44jibm+HqTmPUuk3hzOXXfeCf4PBzBrpdGp46SmsXWp94L+jr4E2Dzm6\/wBlhcNTq8oLKW5y9YtjnFD4iXFu1Y6cKJ8n7mA\/I\/MLDAnLrfSCX4PYiNo9K38hSnwdP5EJ0P8AxqVKhAlR4PH0B8x7znIigpRQtU6D\/nKviCOjYe3AZZUOilT4gIlBlYIEWxVu2VMcwh6Rx8QrRmAFdKMdQPf\/AAekcfEK0ZgBXSjHUD3416+psl16Q9FEVjOl2J\/OJrZuHNWic2Fj08+FaOZTpSXcQlQQrFlphLyw1bbhzVolClnZ1P5c32Ygu6XcNoaNRWq6dHIbQ2GaRwxvr3PCALm7N\/PlwPdi5Z6jjf8AipUqVKlf47XnPHhtevmTv\/UWG17unkRoXkHLrLm0XMuL1Paew94\/5PeYHzL0rgHmePj8RdS583nNGnUnpEX9pc0RUuq5+c7\/ANSl1zWlfMqVKhpP1T6n7p9RA0O6flg8qyvNnJRtprEOT1bvHLQgxY49UpIoqjo84MuoTuukZMILW\/hNRIYLZzIgZ0p7xFFKUPKciueX\/ZjofEvw1lRDAY\/kIRoIqWmzkQ0XmofMLo2PEnqPiPrw09oiS1qxmtUXFjq0HhvGrYKf7NS8fidROPBlfNlfyc4qt88znFVnlmeF1f2dRPoTzGXdfEoiY0f60hISOtvqCw5jw0PCPPL6HKZhtvz0i6qvBS66z9dLAjcFY85k52Xx\/uuASuAOLxqVPYfMe85ylSL3KKSoVEmzRn9C40ZNwav5NVR7JilxNUkUeSkt+DgeWVoGl5wo7S1Sywb+LMrIy1soN2Bt6IZ04mYW7UIQxuMmNS\/disTeWjzJWXKE2dCKwQwpqsLFFl2qAsgtpszKBVACR2NPI+pSCjdhGDq0y5SlUOTCID0QxIyZY3RYLfFiiy7VAWQW02ZlAqgBI7GnkfUpBRuwjB1aZcoeT23TZ38xFzoAALKVAMeMRdldGGKwA75zCL6SAq1lDo\/MPYFGtZSU3ObbwuBQDMmR01hcfKNwzBL6ltBz1g2Cr2Vlq7M0stgTk2LUCi+SXKC3BbC\/4i5kCDt9WPMXpylDbYfc3hz31Je8EqZT\/QDbfQi1ghmU687nTho8n24Xu5cs9Z\/41xqVKlTvec3lLvA8uc7B9ILSvLHtUvgrDzJiuTLhsdBfiKvGPedm5ztHLhaZJ00C+EJK2DB6XD5S7ds\/uXAmCYMH1Glgy0OnKdp0f8GkbvnS2W6Mj1hQmuPgmC5r6RB0mq6QxwJrufmHcydWvudQFVdz2E7LpO06sNY6vDgeDXnv\/wBnMzlh66TEAdn6YOpJE\/syA5PA2RzJqeE9R8T23ucPQIzzEtd+nzKev4hEbY\/MrvIX4bznFVHnieEVPliUjzVeG0vzY18yi9nsc4GR4GYPQm2MvTcNDwjrPRECCo4xPwT7n5J9woURvP7\/AOJD\/L\/n2HzHvOciKClFC1ToPHLg55NXzrSZVcwT0LT3mVfEEdGw9uAyyodFKnxARKDKwQItirdsqY5hA5pO0vdfqIAV0ox1A9\/8BzSdpe6\/UQArpRjqB78XIBAN6Yduow9FEVjOl2J\/OJrZuHNWic2Fj085lVzBPQtPeON1yjFmj1IhKghWLLTCXl4nK6AbrUFiGsLCMiirOjnebeGjyfbhO7xyz1H\/AMHa85p8\/wAf5DnPQRhm8xcFUecV7xg\/SEChrq+J2bnEC1UH8ncftEMA81v2hHQ3QOgB5Qr2t\/kzQg\/wjG1zy\/7N09X2Tno1+BO86P8Ag0jq8K09R4k69y8t4ivdMQWZKuucfoR1wPWUNrdV6z2E7LpO86sNY+yU1xtSOmcReY0aCLW8gs\/pFJqW+iGkajMMXz8d4jtPLU8pXnTIWtw\/DDznqPiex9yEDRdENPNYfiVPMiUGoNkQA0czxVlngrJARaGYiLVXENXKLaaSsv8AImluZ1eW3nPScNDwjrANeQwHXk8mZovUyf2CqGwc1KVEZoVr4f8ArcWX\/n2nzHvOcpUi9yikqFRJs0Z\/QuNGTcGr+TVUeyYpcTVJFHkpLfg4HllaBpecKO0tUssG\/izKyMtbKDdgbeiGdOJmFu1CEMbjJjUv3YrE3lo8yVlyhNnQisEMKarCxRZdqgLILabMygVQAkdjTyPqUgo3YRg6tMuUpVDkwiA9EMSMmWN0WC3xYosu1QFkFtNmZQKoASOxp5H1KQUbsIwdWmXKHk9t02d\/MRc6AACylQDHjEXZXRhisAO+cwi+kgKtZQ6PzD2BRrWUlNzm28LgUAzJkdNYXHyjcMwS+pbQc9YNgq9lZauzNLLYE5Ni1AovkmVSso6hYYAaK9IXjDYCHUvRH1AB3Lcz0a5ss2E0cObBfhMghpU5mO6jbbho8n24Ru8ck9R\/53Ll8fDhvWnlPHpvevkcKlSpY7xNnxm9T+vep+SfcZZT2aRAlrqzcmw14cQDv\/EJs5mC+IosezqeDKeE9KfklLKeTR8sUNaO3fxZXaZu7v4P8d\/44sRuuAFYOHsQy9DSUc4+b3qUMp60fcNgfBrH8Z0UVWsv9U3rXxKPSFa3w013fzgWBU33\/wCz8E+5iqjs0iqci6ssWl1WjCvCelP1KGdeuPuWV8DYmDL5Jprve\/8AkE2hWNCtZrMQl29IVCsOtxiqeLe6+JkhyBrMRORI\/iku\/iYjmC3WES30PuGteZ9T7jYrf224FCu7+cakVabJO9PuY6o7NIlKWur\/AOVSv9VK4e0+Y95zkRQUooWqdB45cHPJq+daTKrmCehae8yr4gjo2HtwGWVDopU+ICJQZWCBFsVbtlTHMIHNJ2l7r9RACulGOoHv\/gOaTtL3X6iAFdKMdQPfi5AIBvTDt1GHooisZ0uxP5xNbNw5q0TmwsennMquYJ6Fp7xxuuUYs0epEJUEKxZaYS8vHDmgibDAa9IWEZFFWdHO8VTI5b++L4aPJ9uAbuvJH\/b3JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7JZ2SzslnZLOyWdks7Jdw95zlKkXuUUlQqJNmjP6Fxoybg1fyaqj2TFLiapIo8lJb8HA8srQNLzhR2lqllg38WZWRlrZQbsDb0QzpxMwt2oQhjcZMal+7FYm8tHmSsuUJs6EVghhTVYWKLLtUBZBbTZmUCqAEjsaeR9SkFG7CMHVplylKocmEQHohiRkyxuiwW+LFFl2qAsgtpszKBVACR2NPI+pSCjdhGDq0y5Q8ntumzv5iLnQAAWUqAY8Yi7K6MMVgB3zmEX0kBVrKHR+YewKNaykpuc23hcCgGZMjprC4+UbhmCX1LaDnrBsFXsrLV2ZpZbAnJsWoFF8kyqVlHULDADRXpC8YbAQ6l6I+oAO5bmejXNlmwmjhzYL8ONbR5PtwDd15IXaQWsDv1J2PonY+idj6J3Pond+idn6J2fond+id36J3fonS9nSdL2dJ3fond+idL2dJ03Z0nRdnSdr6J0XZ0nSdnSdJ2dJ2Pona+id76J3vone+id76J3vone+id76J3vona+idj6J2fonZ+idn6J2fonZ+idn6J2PonY+idj6J2PonY+idj6J2vone+idN2dJ3vone+id76J3vone+id76J3vona+idF2dJ2vonY+idj6J2PohuDs6TQXs8IvpTs5Tp5hds2UKzdsnpOh7Ok7v0Tp+zpG\/R2dIJrseXAAvyIisjs6TpOzpOg7OkItDs6RJydnKdJ2dJ0nZ0nc+idz6J0nZ0nc+idj6J2PonYH1Ox9E7H0TsfRO59E6Ts6TuD6naH1O0Pqdj6J2PonY+idj6J2PonY+idj6J2PonQdnSdj6J3volirqqwHtMp0FTGmqVg1XPwnsh9Tsh9S\/A58l860mVrQTRoWnvOmbrQ+p2Q+p0\/daHjyg2DPwPqBHFaAFkX1RVzow6cusovPOxtnlOyH1OyH1OyH1L6oq50YdOXWUXnnY2zynZD6nZD6lNFhhDUyOkRRQ8mfUE7IfU7IfUpr1g6G\/lEmSa4PqZWtBNGhae8cCK9pR05dYgG7SuTM7IfU7IfUA0rFrGTTWuspwMLxT6hEzchXPUturw0eT7cA3auWbvi+\/CiUSiJKlEo\/xX+V2S+zoihqyzXadJOklNDcGu6+U2YM9EaYYoatSyr2gjo3w7jU5iTqEw6S+aaaywmxvLDVjRriYdOF80vml6rxOoSxqymqzfWpZVssq7xzgjpnhg1nLb1cUNWuHUJ039m7ZLdyCtG4hhQijVUqrvKukJ6kCjdzZlpxMDHWOsuyQg0oGZo9JQykadEysNnRDaG2nWZKsvlFDVqaT8\/CYKq\/+OpUqVKnsJ76CZptzPTzSrkRXjerjIpweffHO95TnBHY1Y8rgteq30Uc4WchYutTDXjHnQvIeG0OcdDOOZVEbPsHniAXTXI3flt5zeVop16Of5AYwKUitlGNY2q0KUAR949YurLqtWvAlku1YbJfXRcVMhdAt0ddPKAxgUpFbKMaxtVoUoAj7x6xdWXVateBLJlSzX6PK4Ato6AX+GZ56cUeYjeJpdBoHq9YLWhsb5NLjqAyXTwZv0l2pFW+jHOWJIyTYvH1KZQ3geqjM8ZgStfwu7j0lBVCl9Y9Yt7ADd4cfUwUTGf9Zg1xhOWHbho8n24Hu1cs3fF95cuXL\/1UqVKlcRfgmVvJ5y7bp7GLF4R0eL8T20o67GfNPZTR8SK7jQfxImrsytKa7nD5J7aUUdtZu2jEdFaZzL34CGF5wsbl+yZh2xKaN\/fh7Eoq7az2cfWfiYq6JTDpmotHIJVc2XKUprucNiHY6Eu7OXsYgXhXnrLAvVP1PQ\/JOzyj7PlmC5X8oXzVPklqNRcvL0+iJz3pmZ3fMWVdYdBRzhVXJmGQuoWjzlYQcspr1nLvlKDedSxqtN5aW5leUy8YnwyofCKm5H3AVoVsfeJW3JNU6\/KVdPHf8\/8AOuFf7rhUqexhh2rDzkVRUoAtU6LOyv3Oyv3AiHWyzoG71fuZWlBF6Gw9p2V+4IrKGpqlTn0iSgGVt+4OJuhbtkmOYQBJUs25po8kEIpRhdwN+s7K\/c7K\/c7K\/cASVLNuaaPJBCKUYXcDfrOyv3Oyv3AMk1FuTR3j0qokTOlip\/J2V+52V+4So3BdVbeWcgsb0ToG71fuVJadVULZvXSWJQIWVlphzlnZX7nZX7g6SgPQKIXcLjrzVEqABGt7wKys14os0U0eGjyfbge7Vyx1fF43\/mpUr\/LssDLeBFEZdcWN1XUh1p2mAWwu3hEYDo2mh4QrVFxGA6DSBpRqzaDxN2mYuqXb\/HlCmeVxrXWFcC4jAcDaZCmn3XKVWcekwy2H3\/JbGrxp3mIWU9XrGwFxiphC71cv5LV0bl0rrAuBMlTT7rlDrOPSYBbC\/EuCmNDW8pVO8egNgTwiqtWnTeoUzyuNa6x2kzUDIKK25VFDFjhXUiX5tPKVYKyvgxCstkr1GbA3lpGvJg5dWJZRsufGWMLjFZuXDeS6PqXTZr9Qq27MIMOupMXDlN3lHQ3DV6SgzZisXrMG7LebaVUBKgq0WdJVa2uPDMyOZviZNaGfrhbVvHRhKQBU2rolnUVTXODKQK7c5oGjwrP\/AM5PYx7znKVIvcopKhUSbNGf0LjRk3Bq\/k1VHsmKXE1SRR5KS34OB5ZWgaXnCjtLVLLBv4sysjLWyg3YG3ohnTiZhbtQhDG4yY1L92KxN5aPMlZcoTZ0IrBDCmqwsUWXaoCyC2mzMoFUAJHY08j6lIKN2EYOrTLlKVQ5MIgPRDEjJljdFgt8WKLLtUBZBbTZmUCqAEjsaeR9SkFG7CMHVplyh5PbdNnfzEXOgAAspUAx4xF2V0YYrADvnMIvpICrWUOj8w9gUa1lJTc5tvC4FAMyZHTWFx8o3DMEvqW0HPWDYKvZWWrszSy2BOTYtQKL5JlUrKOoWGAGivSF4w2Ah1L0R9QAdy3M9GubDWjRSsozo3l7gFiUgF1R14aPJ9uB7tXLHV8X\/wAyXL\/zUr\/yQSnSFBRp\/pZvfnAG9+evvxAU6f8AkglOkACjT\/zuFii1KEy6wCW84FLyhrBmzDE3hFwQlOjNiqGsNZadL\/6ye1j3nORFBSihap0Hjlwc8mr51pMquYJ6Fp7zKviCOjYe3AZZUOilT4gIlBlYIEWxVu2VMcwgc0naXuv1EAK6UY6ge\/8AgOaTtL3X6iAFdKMdQPfi5AIBvTDt1GHooisZ0uxP5xNbNw5q0TmwsennMquYJ6Fp7xxuuUYs0epEJUEKxZaYS8vHDmgibDAa9IOcrmqJUAFNb3hhyxRWNXuTR58NHk+3A92rlloKK63zTlP0X6n6L9T9F+p+i\/U\/Rfqfov1P2X6neP1O0fqdo\/U7R+p3j9TvH6neP1O8fqfuP1Ot6vqdb1fU6nq+p2D9Tq+r6neP1O8fqd4\/U7x+p3j9TvH6neP1O8fqd4\/U7x+p3j9TvH6neP1O8fqd4\/U7R+p2j9Tq+r6nYP1O0fqdo\/U7B+p2D9TsH6nU9X1Op6vqdT1fU7B+p2D9Tqer6nW9X1O4fqdb1fU63q+p1vV9Trer6nW9X1Ot6vqdb1fU63q+p1vV9QOvc+pns\/oxzP7QK5\/sx862Hb\/vD9OH68P04H\/fghn+zH\/afqdb1fU63q+p1PV9QvF8Vj7QgZt\/swIq\/V9Tqer6nU9X1Op6vqdT1fU6nq+p1PV9Tqer6nU9X1Op6vqdT1fU6nq+p1PV9Tqer6nU9X1Op6vqdT1fU6nq+p1PV9Tqer6nU9X1Op6vqdb1fU63q+p1vV9Trer6nX9X1Ov6vqPMM8o95zlKkXuUUlQqJNmjP6Fxoybg1fyaqj2TFLiapIo8lJb8HA8srQNLzhR2lqllg38WZWRlrZQbsDb0QzpxMwt2oQhjcZMal+7FYm8tHmSsuUJs6EVghhTVYWKLLtUBZBbTZmUCqAEjsaeR9SkFG7CMHVplylKocmEQHohiRkyxuiwW+LFFl2qAsgtpszKBVACR2NPI+pSCjdhGDq0y5Q8ntumzv5iLnQAAWUqAY8Yi7K6MMVgB3zmEX0kBVrKHR+YewKNaykpuc23hcCgGZMjprC4+UbhmCX1LaDnrBsFXsrLV2ZpZbAnJsWoFF8kyqVlHULDADRXpC8YbAQ6l6I+oAO5bmejXNhrRopWUZ0by9wCxKQC6o68NHk+3A92rlhlEjgpzdZ+6R+6R+6R+6R+6R+qR+gR+yR+gR+gR+gR+kR+kR+kR+0R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+kR+sR+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8R+8QlqEFmkAhqH+RIIMABqF4JHjPT7nYEfvEOmPinEQAWaf2I\/eI\/eI\/eI\/eI\/eI\/eI\/eI\/eI\/eI\/eI\/eI\/eI\/eI\/eI\/eI\/eI\/eI\/eI\/eI\/SI\/SI\/SI\/SI\/SI\/SI\/eI\/eIPEjXL9Y95zkRQUooWqdB45cHPJq+daTKrmCehae8yr4gjo2HtwGWVDopU+ICJQZWCBFsVbtlTHMIHNJ2l7r9RACulGOoHv\/gOaTtL3X6iAFdKMdQPfi5AIBvTDt1GHooisZ0uxP5xNbNw5q0TmwsennMquYJ6Fp7xxuuUYs0epEJUEKxZaYS8vHDmgibDAa9IOcrmqJUAFNb3hhyxRWNXuTR58NHk+3A92rlmp4v8A51KlSpUqVKlcKlSpUr\/VSpUr\/wC6uCFRz0mmyyhHcLhLOmsCxrzmk0SlbrGmYam9U0ioHgqVK\/8Ao9jHvOcpUi9yikqFRJs0Z\/QuNGTcGr+TVUeyYpcTVJFHkpLfg4HllaBpecKO0tUssG\/izKyMtbKDdgbeiGdOJmFu1CEMbjJjUv3YrE3lo8yVlyhNnQisEMKarCxRZdqgLILabMygVQAkdjTyPqUgo3YRg6tMuUpVDkwiA9EMSMmWN0WC3xYosu1QFkFtNmZQKoASOxp5H1KQUbsIwdWmXKHk9t02d\/MRc6AACylQDHjEXZXRhisAO+cwi+kgKtZQ6PzD2BRrWUlNzm28LgUAzJkdNYXHyjcMwS+pbQc9YNgq9lZauzNLLYE5Ni1AovkmVSso6hYYAaK9IXjDYCHUvRH1AB3Lcz0a5sNaNFKyjOjeXuAWJSAXVHXho8n24Hu1cs1PF\/8ACpUqV\/g\/zUriVKlSpUqVKlSpX+q4VKlSpUqVK\/8AZxCahCQ30l0zLMkBmyhQJe5rMy7xkpMVbmWeGVcJpN+QTSV\/iuFf\/L7WPec5EUFKKFqnQeOXBzyavnWkyq5gnoWnvMq+II6Nh7cBllQ6KVPiAiUGVggRbFW7ZUxzCBzSdpe6\/UQArpRjqB7\/AOA5pO0vdfqIAV0ox1A9+LkAgG9MO3UYeiiKxnS7E\/nE1s3DmrRObCx6ecyq5gnoWnvHG65RizR6kQlQQrFlphLy8cOaCJsMBr0gCLth0FcnNyljYjV4cWacNHk+3A92rlnqH\/ZxrhRK4KlSpUqVK4KlRJUtKZUqUyn\/AMKlSuNf5qVK\/wDGo3FZlGIocKiqGJjfgNshMx1lyiJe1E06Tl7DaCaGCUOsaFtW+KCnlx1R\/wC1f4rjXCpU9jHvOcpUi9yikqFRJs0Z\/QuNGTcGr+TVUeyYpcTVJFHkpLfg4HllaBpecKO0tUssG\/izKyMtbKDdgbeiGdOJmFu1CEMbjJjUv3YrE3lo8yVlyhNnQisEMKarCxRZdqgLILabMygVQAkdjTyPqUgo3YRg6tMuUpVDkwiA9EMSMmWN0WC3xYosu1QFkFtNmZQKoASOxp5H1KQUbsIwdWmXKHk9t02d\/MRc6AACylQDHjEXZXRhisAO+cwi+kgKtZQ6PzD2BRrWUlNzm28LgUAzJkdNYXHyjcMwS+pbQc9YNgq9lZauzNLLYE5Ni1AovkmVSso6hYYAaK9IXjDYCHUvRH1AB3Lcz0a5sQuVbS9yoAhToPA0eT7cD3auWani\/wDhcuXwIcalSv8AF8alSpUqVKlR\/wDKpUr\/ANKm0rhlxK8wwQbxDLYhtOhOhLVpK1MU6S\/NLg2rMBYgmbw\/wI3ygqxbqFuGn\/VR\/wB1wVKlSuFSuA1B7znIigpRQtU6Dxy4OeTV860mVXME9C095lXxBHRsPbgMsqHRSp8QESgysECLYq3bKmOYQOaTtL3X6iAFdKMdQPf\/AAHNJ2l7r9RACulGOoHvxcgEA3ph26jD0URWM6XYn84mtm4c1aJzYWPTzmVXME9C09443XKMWaPUiEqCFYstMJeXjhzQRNhgNekARdsOgrk5uUsbEavDizTho8n24Hu1csdXi\/8AmQh\/uv8A1r\/4XgypUqVxCMVGDeY6IlBFiEGkomOUxymJ4RhZmaLK0Jggjzms04sDSayn6zbFrgJT\/FS4OBS8vB8NpYlMqVKlSv8AMe85ylSL3KKSoVEmzRn9C40ZNwav5NVR7JilxNUkUeSkt+DgeWVoGl5wo7S1Sywb+LMrIy1soN2Bt6IZ04mYW7UIQxuMmNS\/disTeWjzJWXKE2dCKwQwpqsLFFl2qAsgtpszKBVACR2NPI+pSCjdhGDq0y5SlUOTCID0QxIyZY3RYLfFiiy7VAWQW02ZlAqgBI7GnkfUpBRuwjB1aZcoeT23TZ38xFzoAALKVAMeMRdldGGKwA75zCL6SAq1lDo\/MPYFGtZSU3ObbwuBQDMmR01hcfKNwzBL6ltBz1g2Cr2Vlq7M0stgTk2LUCi+SZVKyjqFhgBor0heMNgIdS9EfUAHctzPRrmxC5VtL3KgCFOg8DR5PtwPdq5Zu+L\/AJqVKlSv8X\/lX+T\/AFXCv\/neFcahGQxH0hZSxgPCyYlzwlWS61DY4HrwobRL0nUl80IXFXmitTleBjK2IWoA4TCpfAHC\/wDC+BJXFmkHj6CPec5EUFKKFqnQeOXBzyavnWkyq5gnoWnvMq+II6Nh7cBllQ6KVPiAiUGVggRbFW7ZUxzCBzSdpe6\/UQArpRjqB7\/4Dmk7S91+ogBXSjHUD34uQCAb0w7dRh6KIrGdLsT+cTWzcOatE5sLHp5zKrmCehae8cbrlGLNHqRCVBCsWWmEvLxw5oImwwGvSAIu2HQVyc3KWNiNXhxZpw0eT7cD3auWbvi+8qVKlSpUr\/BxYrflMwKzpp9ypUqVK41O56Q2Q98oi1TiBhaVKlSuDNNwBZ\/q\/wDVRJUqVKlSog1vGJGmGc8QipzTqTBJV0LObXMeYA24BoDTVNP6pGqHDb8hDLbXl9TtD9TQ9GIBtDC5fFGs8aJ2ibZvCMsVKmNJUCQm48AZSal5DMrxWRuEZaWlMzLj\/j0ke85ylSL3KKSoVEmzRn9C40ZNwav5NVR7JilxNUkUeSkt+DgeWVoGl5wo7S1Sywb+LMrIy1soN2Bt6IZ04mYW7UIQxuMmNS\/disTeWjzJWXKE2dCKwQwpqsLFFl2qAsgtpszKBVACR2NPI+pSCjdhGDq0y5SlUOTCID0QxIyZY3RYLfFiiy7VAWQW02ZlAqgBI7GnkfUpBRuwjB1aZcoeT23TZ38xFzoAALKVAMeMRdldGGKwA75zCL6SAq1lDo\/MPYFGtZSU3ObbwuBQDMmR01hcfKNwzBL6ltBz1g2Cr2Vlq7M0stgTk2LUCi+SZVKyjqFhgBor0heMNgIdS9EfUAHctzPRrmxC5VtL3KgCFOg8DR5PtwPdq5YGvi+8qVKlSpUqVKlSuCWg1mTRjoBg3iijTVgzu6dktxnrX\/YaCUaXMQtVd10jq69uXCzPZ+YtO\/N2R2nXaaHhDV9PGCKKckIwbDnNKTlwborM5xJrDU\/pwbA\/sQqr9PiVzz2j4ctURDC3y+oNrS84lMN1rCFhiLQW6VAUYt5Staw40ikzmNwVXOIJQvj\/AMi6bZgbuG2WXIjSAS+eojRbL\/3NyOrMEyrYJqOk1PCEEJ4wMhbWAJG2aida5qrPhwxBbn+Tbg1Tn7jruibz28Ynw0UQVFF8\/mWjke\/hBb8I0fEfSNy0lWb9o4W6vofUrn9xrHKqWhYDouLaelK0iSjCNAuu+8QDOZoGnOLirfH\/AJMy85lKI1waQUDjUrKSso\/wHvOciKClFC1ToPHLg55NXzrSZVcwT0LT3mVfEEdGw9uAyyodFKnxARKDKwQItirdsqY5hA5pO0vdfqIAV0ox1A9\/8BzSdpe6\/UQArpRjqB78XIBAN6Yduow9FEVjOl2J\/OJrZuHNWic2Fj085lVzBPQtPeON1yjFmj1IhKghWLLTCXl44c0ETYYDXpBylMlRYUFGt7w7ln1Ssxe5PXho8n24Hu1csOPN9+JXBX+KlcKlRN2JpriWFdXrEGy4uAnjMoC+UHaPVM78HSjzlBmjey97uukXYy8JqhgwV05TIGPjUNU3ujgzt9H\/AGOOtPl7RpW031YFFcp5mZ8s53SVAM1FyMMOZK6bxLlo2he7cOjQ27YmHzVpNKGNNI62BpAmLsJomsFa4qzRctd8C7oLfDlFQw2dsFoGVrUoA5Stq849tUdlO0J96xHdVNbVEbLMHA0BgmrtdYjWNYJfDz84Abrkyw1yEdPFjajbV\/UUaw1pX3DMt7u9n+ZitLOqr8ShW8MjL+V\/ZbvQbTAF6QBKa7wCAeFqb5OUrjeYltN50vEdpzLFr\/IxxVdY8S4iXCHBnL4SExPHBDB7znKVIvcopKhUSbNGf0LjRk3Bq\/k1VHsmKXE1SRR5KS34OB5ZWgaXnCjtLVLLBv4sysjLWyg3YG3ohnTiZhbtQhDG4yY1L92KxN5aPMlZcoTZ0IrBDCmqwsUWXaoCyC2mzMoFUAJHY08j6lIKN2EYOrTLlKVQ5MIgPRDEjJljdFgt8WKLLtUBZBbTZmUCqAEjsaeR9SkFG7CMHVplyh5PbdNnfzEXOgAAspUAx4xF2V0YYrADvnMIvpICrWUOj8w9gUa1lJTc5tvC4FAMyZHTWFx8o3DMEvqW0HPWDYKvZWWrszSy2BOTYtQKL5JlUrKOoWGAGivSF4w2Ah1L0R9QAdy3M9GubKBSdQt2C8C1DzQQxrYJgOGjyfbge7Vyz3n3f81KlSuFSpUqVLGqHBXG7ZZ5cU6wDo3N+GIob8CNM\/8AnUqVWuOBIWbKuUNWoyp1jgszwJXBrq1B6xJGxhgO6zqgEQOUp5y3OUNNWDrFiJGG46hbltQTCaxUWOlevCQ2ildR2hCOYCBq46jlnquBSjB5weBd8C5aS0KTLhmN8ROGEs5R7znIigpRQtU6Dxy4OeTV860mVXME9C095lXxBHRsPbgMsqHRSp8QESgysECLYq3bKmOYQOaTtL3X6iAFdKMdQPf\/AAHNJ2l7r9RACulGOoHvxcgEA3ph26jD0URWM6XYn84mtm4c1aJzYWPTzmVXME9C09443XKMWaPUiEqCFYstMJeXjhzQRNhgNekHKUyVFhQUa3vDuWfVKzF7k9eGjyfbge7Vyx4833l\/6qVxqVKlQeqFvF1g4T16ebUuAoRYGN3\/ADE35mDupZLq3uFchGmnKCqlrmLbykpzWsa8GY0BLYuYzzVmXOTvzl8bvOWzmfH\/AJFa1YC19pd9YxA3VfzMy9QgnuGZXAN\/z3jmjOlZ9ImA6Wa8tZcrQ785cCVzbjNjaJc2LM4XSFy9ppFI6TNB6wDAXFwCuqBoc7jyD4MavlCjPaK+KNjLcHJogAxLcpsVZvHShq0mwisK3nTltrLxrwswlRqpnruXQcVHPh9xpsFRTLuEaJ3mOJ51gzDkxgtscf2IOS5jKjDvKX1ihUs2hbayp\/JZEGBB3MI04JDcm04WQi+AEGoQ1UFeSPec5SpF7lFJUKiTZoz+hcaMm4NX8mqo9kxS4mqSKPJSW\/BwPLK0DS84UdpapZYN\/FmVkZa2UG7A29EM6cTMLdqEIY3GTGpfuxWJvLR5krLlCbOhFYIYU1WFiiy7VAWQW02ZlAqgBI7GnkfUpBRuwjB1aZcpSqHJhEB6IYkZMsbosFvixRZdqgLILabMygVQAkdjTyPqUgo3YRg6tMuUPJ7bps7+Yi50AAFlKgGPGIuyujDFYAd85hF9JAVayh0fmHsCjWspKbnNt4XAoBmTI6awuPlG4Zgl9S2g56wbBV7Ky1dmaWWwJybFqBRfJMqlZR1CwwA0V6QvGGwEOpeiPqADuW5no1zZQKTqFuwXgWoeaCGNbBMBw0eT7cD3auWafN9\/8Zhw1mdB\/wBDB48SwZqV4XEUNZtubY5sqL45zQIxnX4htWptCqWpYYg\/k8ASvCZjd8fqAerk8I2GgZbWZ7yGLbW8+BzTMTRIRf5REBlvMS40E1qpuzTXxmyKbasy1U3ZprDEUrzigVH0RgVut5ne+N4xCgwQq2QMJb8l7X\/IwDgmcvl8Qz3rrMJkxwHYqC009YyuRRKFA6bS6u65SuZHyjyPNrEZXpcMrl\/fWGaDfhMmSaQHrMttXlAlcFOkstc3txUC0WW8s1d68JrOTmBY\/hGXrESuSZao4+MGvjmMqjfjOyB85ZT7zmJXpKbm\/BhlOYpqhus30xKAphlec05wxGCI\/wCAXczLxwuWlpbC5hDCWHhHvOciKClFC1ToPHLg55NXzrSZVcwT0LT3mVfEEdGw9uAyyodFKnxARKDKwQItirdsqY5hA5pO0vdfqIAV0ox1A9\/8BzSdpe6\/UQArpRjqB78XIBAN6Yduow9FEVjOl2J\/OJrZuHNWic2Fj085lVzBPQtPeON1yjFmj1IhKghWLLTCXl44c0ETYYDXpBylMlRYUFGt7w7ln1Ssxe5PXho8n24Hu1cs0+b7\/wCMMqFQzgbS\/Gry0t\/4AAFpaM2nJOl4Wu8FhOsBzmSVwEsmNp4zQ1h2MQNFUuNY8KnDlGqlkqQrjyy8uOBXWHYqFbmlZlqdU2w2ss1iekNiZ6QeEs4t1MSy7gUMEjwRcYZDrwCsIOWXNoQzpwr\/AMCi4GAJdLGMlEuohlDA1MngVwe85ylSL3KKSoVEmzRn9C40ZNwav5NVR7JilxNUkUeSkt+DgeWVoGl5wo7S1Sywb+LMrIy1soN2Bt6IZ04mYW7UIQxuMmNS\/disTeWjzJWXKE2dCKwQwpqsLFFl2qAsgtpszKBVACR2NPI+pSCjdhGDq0y5SlUOTCID0QxIyZY3RYLfFiiy7VAWQW02ZlAqgBI7GnkfUpBRuwjB1aZcoeT23TZ38xFzoAALKVAMeMRdldGGKwA75zCL6SAq1lDo\/MPYFGtZSU3ObbwuBQDMmR01hcfKNwzBL6ltBz1g2Cr2Vlq7M0stgTk2LUCi+SZVKyjqFhgBor0heMNgIdS9EfUAHctzPRrmygUnULdgvAtQ80EMa2CYDho8n24Hu1cs0+b7\/wCK4EZpeCsyrZf\/AHBWVmPBlw24BmOGDxARuU3hHastmfliOxF2lE0WDHaz2h8nMv3mDWOSLGXKVeeUplbOATk05xGkzpUXLNJmz\/N42wQmTF+SU1LTRCucww1do\/kTdYnVOYaS3hNUv7LJd6xUs1lmzV4RymCoBaKhmcSG8esomymTbNJGeCdCBejyhhVjnNXeZZiBsiH+TcYJLJjwG8VZTwhQVFxPYx7znIigpRQtU6Dxy4OeTV860mVXME9C095lXxBHRsPbgMsqHRSp8QESgysECLYq3bKmOYQOaTtL3X6iAFdKMdQPf\/Ac0naXuv1EAK6UY6ge\/FyAQDemHbqMPRRFYzpdifzia2bhzVonNhY9POZVcwT0LT3jjdcoxZo9SISoIViy0wl5eOHNBE2GA16Tk44icbUrXeoRcsUVjV7k0efDR5PtwPdq5Zo8334pesth5kHWsBFjrGLg6KbALjzjhfB4EWKEOJUYw1mqHB4kKQO3qKJe7\/uKTe3lhrpM+w9oVuQujlpKgW8rc3gKiGNG2pExDfOBtO7CZTRz9wP7QUyuVYMMvOmDM7B0hg8kDU\/krkEvIc4OjlLL67QMgdBNC84b9JgjT3RTFq8YSaTz39JuktOiCthYe6mhTdRNaRDhWBuYSYWinJEsUEDCqgpohRlBqWS61wGpEDE1XKjFcBYwzG+BLhCHhZGWFfgEe85ylSL3KKSoVEmzRn9C40ZNwav5NVR7JilxNUkUeSkt+DgeWVoGl5wo7S1Sywb+LMrIy1soN2Bt6IZ04mYW7UIQxuMmNS\/disTeWjzJWXKE2dCKwQwpqsLFFl2qAsgtpszKBVACR2NPI+pSCjdhGDq0y5SlUOTCID0QxIyZY3RYLfFiiy7VAWQW02ZlAqgBI7GnkfUpBRuwjB1aZcoeT23TZ38xFzoAALKVAMeMRdldGGKwA75zCL6SAq1lDo\/MPYFGtZSU3ObbwuBQDMmR01hcfKNwzBL6ltBz1g2Cr2Vlq7M0stgTk2LUCi+SZVKyjqFhgBor0heMNgIdS9EfUAHctzPRrmxgzFtC2kyDfylGAu5L4yetz9uGjyfbge7Vyyn9PuxiqHVGzWPMnUhzo2uvC4isg7rLl8XgyollIFcTDWOsOGiH+rcF1bl5sGUIPHgKrGmc5QRnBnOUc6wl+c1nIajzNzJmCxgBnIQFdZnE9EG9y28G85cRdZSLw7Qo2AfCHzkzTQNgqW6gwAVKgby1OlFohxdzVrKOUpCRHWFcQrhgcuWEHcY2ah\/xh4sM6YcSwZRwOE1Q0nsfmPec5EUFKKFqnQeOXBzyavnWkyq5gnoWnvMq+II6Nh7cBllQ6KVPiAiUGVggRbFW7ZUxzCBzSdpe6\/UQArpRjqB7\/wCA5pO0vdfqIAV0ox1A9+LkAgG9MO3UYeiiKxnS7E\/nE1s3DmrRObCx6ecyq5gnoWnvHG65RizR6kQlQQrFlphLy8cOaCJsMBr0nJxxE42pWu9Qi5YorGr3Jo8+Gjyfbge7VyxEMDNyxSZiGVKjwuM8VUM5ZLly+Fy4Er\/BmqOvFlcFg9DH\/YyM2MxwzLNC5fNcYrHvcapp4XFbk6TeAbxhZg0gxZADD4yxqt8pYzZ2jZNdYWOdIo6RGjEpyFrXNHsxKq2MwVwwW\/CAyGpSLYidIhtxaNZXHAqVMkNjMRAQEo7TpTAxSOYQjFYiS44BC4ZwMpN4HBjThwQXMB4GBLlwlyickd+CfMe85ylSL3KKSoVEmzRn9C40ZNwav5NVR7JilxNUkUeSkt+DgeWVoGl5wo7S1Sywb+LMrIy1soN2Bt6IZ04mYW7UIQxuMmNS\/disTeWjzJWXKE2dCKwQwpqsLFFl2qAsgtpszKBVACR2NPI+pSCjdhGDq0y5SlUOTCID0QxIyZY3RYLfFiiy7VAWQW02ZlAqgBI7GnkfUpBRuwjB1aZcoeT23TZ38xFzoAALKVAMeMRdldGGKwA75zCL6SAq1lDo\/MPYFGtZSU3ObbwuBQDMmR01hcfKNwzBL6ltBz1g2Cr2Vlq7M0stgTk2LUCi+SZVKyjqFhgBor0heMNgIdS9EfUAHctzPRrmxgzFtC2kyDfylGAu5L4yetz9uGjyfbge7Vyz1\/8Ai5cuEo4L\/wALmUUgy5bLYKW8XGapvK\/wo7Ve8zpBoW4bOLsUPOa5DA01ZUV2XWltaYilHQoBnylhRZlc\/sLRzOvxCiB2sKfdiotD0cko7PUMH8l0oDH+zxRkVho6aTHSjQqCaPJ+pnHcIhzpwljTJtAK73HlGsbzW72hyvynOKEsjrZOQjm3DGKeEV+RlNPODtR1mBeQ6VFuk6tzK\/dUL6uYaLE1y3U2sNR7u8LygNqHux0cdGarzqMdHUiUwiwwKlHXg1K0N5cphlmACCb+CcrhqMXCDPAojS5ni0f8GDUrATebxdjrHvOciKClFC1ToPHLg55NXzrSZVcwT0LT3mVfEEdGw9uAyyodFKnxARKDKwQItirdsqY5hA5pO0vdfqIAV0ox1A9\/8BzSdpe6\/UQArpRjqB78XIBAN6Yduow9FEVjOl2J\/OJrZuHNWic2Fj085lVzBPQtPeON1yjFmj1IhKghWLLTCXl44c0ETYYDXpOTjiJxtStd6hFyxRWNXuTR58NHk+3A92rlnr\/\/AAYvg1B4Ckw4AzqwhiASj\/PeXmH+dGg9C6+kbFa1Ac4vtCrrXQPuLa8zf7i4rTJ0gYVGc39zVd1oQWfnENsvLNntESwmkSU4eBOl285ZvXl9yhMDpTp6DEb9qrlBwGgZ3jbbKjDOdvMMqRrCKcUPOs+stW1bLmpacqhpamlQMwLC9dTaA1p4QIqh8YbpBnG4T9v3gvJO+sWu3LY5mt9ZkiDzQKK4MoSjeI7xxhj2COk1humq5fmNAx+hwlcYw4Fkv1mes5Es5yzWJ24JGEmIM2mVgp8Hyj3nOUqRe5RSVCok2aM\/oXGjJuDV\/JqqPZMUuJqkijyUlvwcDyytA0vOFHaWqWWDfxZlZGWtlBuwNvRDOnEzC3ahCGNxkxqX7sViby0eZKy5QmzoRWCGFNVhYosu1QFkFtNmZQKoASOxp5H1KQUbsIwdWmXKUqhyYRAeiGJGTLG6LBb4sUWXaoCyC2mzMoFUAJHY08j6lIKN2EYOrTLlDye26bO\/mIudAABZSoBjxiLsrowxWAHfOYRfSQFWsodH5h7Ao1rKSm5zbeFwKAZkyOmsLj5RuGYJfUtoOesGwVeystXZmllsCcmxagUXyTKpWUdQsMANFekLxhsBDqXoj6gA7luZ6Nc2MGYtoW0mQb+UowF3JfGT1uftw0eT7cD3auWev\/1cuLBrMf57zTNRJpLhUAbL1+4p\/wBn3FdFJCpTgQoWxZL3lcHi3m8GPFzVe+cNLGdjjVtRZbDr9x4i2j+\/f+DMeZfwMTxOf\/1X+DZBOkGc2ZQaytYuPNA9f5A70ObEZwkAY3JZL5NDebys75xWnBo2jTaJGodejxg3WnOL0tNWGSk3DPulSlaOkczp30gC6NLsfuZCag9R9wNvB0\/Lj9Mqys+9S28zbjn\/ACaY06R04AaQZIghkWwQCWGseNhKQ3EfKC7RkHY9+Ut4dAiO0bNYwvHFAxS2K\/B+Ue85yIoKUULVOg8cuDnk1fOtJlVzBPQtPeZV8QR0bD24DLKh0UqfEBEoMrBAi2Kt2ypjmEDmk7S91+ogBXSjHUD3\/wABzSdpe6\/UQArpRjqB78XIBAN6Yduow9FEVjOl2J\/OJrZuHNWic2Fj085lVzBPQtPeON1yjFmj1IhKghWLLTCXl44c0ETYYDXpLAAaq7M6OZlhky6q4aPJ9uB7tXLPU\/4t43AHyZh15JX3LzSlaGvrDBYAXM80lEYf4\/1ZUoMQmXxm3R0Op9oqjyiUOqOof7pHgUQaH0iivHsnvBlMOfq5zNmsaQFXDeDWXdLOssqFsiLj+ywqeZuoRUit4fhzMTG9u+URkOSnV8GVad+b4jY6zntlGDRdrfsguY1X0gLxFtvqOF7Ha7qDO46YiZM2wsolfPXwEADzumJ66a\/N8cKGXzVql7ec6a88sKcit7f5Ac2jY\/rAh1Pe0arIXxXhfBL9Iej8M1Iryf8Ak5g9A5ec5svJ\/wCQ\/wA5UsLTfmeUS2z0psv+EKG1NF88wcN7sa+pnjzf8iKqhtt9zSaHgPOEvI7B8MXFFjB8UzHGzEbomRoPWNemsoRQ6MHNeY\/5Lw54edRV4zceOdnrMoFXVGvSo41ysykg0hn3gL7ap5wtbpo335QsBb2bLP5BVbbKU81pa2PGoB3sbOyawvygOr4xKRehDWW5XW0cLYqAO1KequZKNeoy7LOI7BSuzQ6\/8nP6uUsbrzf+SugFOev9nii9kWWWaxs5Y7pLVpc16RxvwuLM0ZnfeMe85ylSL3KKSoVEmzRn9C40ZNwav5NVR7JilxNUkUeSkt+DgeWVoGl5wo7S1Sywb+LMrIy1soN2Bt6IZ04mYW7UIQxuMmNS\/disTeWjzJWXKE2dCKwQwpqsLFFl2qAsgtpszKBVACR2NPI+pSCjdhGDq0y5SlUOTCID0QxIyZY3RYLfFiiy7VAWQW02ZlAqgBI7GnkfUpBRuwjB1aZcoeT23TZ38xFzoAALKVAMeMRdldGGKwA75zCL6SAq1lDo\/MPYFGtZSU3ObbwuBQDMmR01hcfKNwzBL6ltBz1g2Cr2Vlq7M0stgTk2LUCi+SZVKyjqFhgBor0heMNgIdS9EfUAHctzPRrmwN3UZBRhV4MF6WuUtSzldduGjyfbge7Vyz1MzMzMzMyotCmsQozZHJwOxymWHVdPsy1jTrmAaw\/DVrDQq3egfLMaJMCq5ztD7nWZc\/eJpi9KAfMzAgZtmHYN5hxfOchXi38EGgutEn9CH4lSybliAKLeu5NRd4B941Aw25x9gjvu67EtAomje03qYONUvAw20heUduUUw6gDHhkmGq5nXhQ9f2tfGECmY6D7zMxebgPLMpBjO1axJSeResTGV8B944DHStMeUHbclZxyieUrvs8KhbjeeSUyz10+5oq2+8win0+5glH9HuTM11rh7LNdHLVud9YwW66CesxnhGhXlMA8Syf1mhhun\/kvV8Jw+8sN500D3jCWvgJ6yo\/iaYhDT7qRQwN9YVc0QVWsHzo6+UyiTQaCeTA1VRVkL83EpgFAbtJbJooWpUHQ6714RVa5gPsRFe23eDXkFwW+1QxcvDzx5aSstRu0PeYid4KqA9SulepHGsZpcY2mio6N585YwX1T8IDmV0ZPciEsjduPuVg5\/wBgYEHj+kuXYdtogELTL8Fc5Lthp4NNXmJyBrwuoZSgjmH+Hyj3nORFBSihap0Hjlwc8mr51pMquYJ6Fp7zKviCOjYe3AZZUOilT4gIlBlYIEWxVu2VMcwgc0naXuv1EAK6UY6ge\/8AgOaTtL3X6iAFdKMdQPfi5AIBvTDt1GHooisZ0uxP5xNbNw5q0TmwsennMquYJ6Fp7xxuuUYs0epEJUEKxZaYS8vHDmgibDAa9JYADVXZnRzMsMmXVXDR5PtwPdq5Yv6y5cuWTEuLLS0uLi8QhnLY80nXJdMmeEBF4cbZQistqWlpbhYPBbEKLzMTaciJq4BFIb4RHiTCc0uaJQQzpwKjCTKICBLTOLI2cRqYiRUCYReKrvjO2K4Y1weDNTHx4XwFMIZiCCIlxmplN4HC7IqLq1mG4o1CBBXuxDzRP7K5wdwFJeVeB8o95zlKkXuUUlQqJNmjP6Fxoybg1fyaqj2TFLiapIo8lJb8HA8srQNLzhR2lqllg38WZWRlrZQbsDb0QzpxMwt2oQhjcZMal+7FYm8tHmSsuUJs6EVghhTVYWKLLtUBZBbTZmUCqAEjsaeR9SkFG7CMHVplylKocmEQHohiRkyxuiwW+LFFl2qAsgtpszKBVACR2NPI+pSCjdhGDq0y5Q8ntumzv5iLnQAAWUqAY8Yi7K6MMVgB3zmEX0kBVrKHR+YewKNaykpuc23hcCgGZMjprC4+UbhmCX1LaDnrBsFXsrLV2ZpZbAnJsWoFF8kyqVlHULDADRXpC8YbAQ6l6I+oAO5bmejXNgbuoyCjCrwYL0tcpalnK67cNHk+3A92rlnrv8XL\/wAXwpeJYYJdETMr41VLRb0hqJzEVpEhtNFxfBbRhKlMxAkZjbSeM1wRWqmHohbxiEBgh\/wYmukHEsvesql9IiBlu0Ya8AmUHArtKQngzlosjZL4XFlrR5XGpjeFDHAsdzGhKjiXdJYlXm1Bh5ihcoaQ5eAbMFyEZltxkK4NxYplzGLXp8x7znIigpRQtU6Dxy4OeTV860mVXME9C095lXxBHRsPbgMsqHRSp8QESgysECLYq3bKmOYQOaTtL3X6iAFdKMdQPf8AwHNJ2l7r9RACulGOoHvxcgEA3ph26jD0URWM6XYn84mtm4c1aJzYWPTzmVXME9C09443XKMWaPUiEqCFYstMJeXjhzQRNhgNeksABqrszo5mWGTLqrho8n24Hu1cs9d\/43wukIAjxNcuKIoYnNKpUGcwVpKIAEbVmSaf4yZRCcLbZscesHUS9cWbnTl3AJmTfhcEhUpFuEfKNKhNcBjFS0hVi4eEwbRWxE9SA3hwyyK4V4uVBm\/K51Bw1JZFKoziODai1ZHYxsphak6mPlMeYvaWsEaa6xNePCVIRlLMSVh5xxFX4Hyj3nOUqRe5RSVCok2aM\/oXGjJuDV\/JqqPZMUuJqkijyUlvwcDyytA0vOFHaWqWWDfxZlZGWtlBuwNvRDOnEzC3ahCGNxkxqX7sViby0eZKy5QmzoRWCGFNVhYosu1QFkFtNmZQKoASOxp5H1KQUbsIwdWmXKUqhyYRAeiGJGTLG6LBb4sUWXaoCyC2mzMoFUAJHY08j6lIKN2EYOrTLlDye26bO\/mIudAABZSoBjxiLsrowxWAHfOYRfSQFWsodH5h7Ao1rKSm5zbeFwKAZkyOmsLj5RuGYJfUtoOesGwVeystXZmllsCcmxagUXyTKpWUdQsMANFekLxhsBDqXoj6gA7luZ6Nc2Bu6jIKMKvBgvS1ylqWcrrtw0eT7cD3auWP+0vjaWlpaW4Akd7IKN4qG8RKxDggxCchGqglpeqgoC6guJGeBN6QqJR4O2pFVS3HPDeaTOVLnCSbcNYA34aI6ZnlAuEI0qEFRLjzi6Q0XwrgXnjaMlOjGs6UHwViZZHPCowEs4LGGNS4Z4PhUtYr4BsgDFMZbaFnMDaUjNeDDFwJcVCeh+Ue85yIoKUULVOg8cuDnk1fOtJlVzBPQtPeZV8QR0bD24DLKh0UqfEBEoMrBAi2Kt2ypjmEDmk7S91+ogBXSjHUD3\/wHNJ2l7r9RACulGOoHvxcgEA3ph26jD0URWM6XYn84mtm4c1aJzYWPTzmVXME9C09443XKMWaPUiEqCFYstMJeXjhzQRNhgNekEcmlWODSvPeF1kdOBNHk+3A92rll3iSkxMf6tiwQjGCKoviOeBlEYYZvAaEyyR0w1iOciNzgszKrbiX0nggcEDmNki10hrJtUzZ0msE26K5QyswjwqFTol4lsvLVLYQBl5YlmM5mPO4Qe8qy6MZf+b4msEZRFTXF4eiJVcKveXgmcJwhQS8GDcbmWCubgCRGtf4gl2yPec5SpF7lFJUKiTZoz+hcaMm4NX8mqo9kxS4mqSKPJSW\/BwPLK0DS84UdpapZYN\/FmVkZa2UG7A29EM6cTMLdqEIY3GTGpfuxWJvLR5krLlCbOhFYIYU1WFiiy7VAWQW02ZlAqgBI7GnkfUpBRuwjB1aZcpSqHJhEB6IYkZMsbosFvixRZdqgLILabMygVQAkdjTyPqUgo3YRg6tMuUPJ7bps7+Yi50AAFlKgGPGIuyujDFYAd85hF9JAVayh0fmHsCjWspKbnNt4XAoBmTI6awuPlG4Zgl9S2g56wbBV7Ky1dmaWWwJybFqBRfJMqlZR1CwwA0V6QvGGwEOpeiPqADuW5no1zY4Vh4AXs322zyndmGelfPho8n24Hu1cst5szwr\/FvAvLxxlmMXimWy+Dw0mswQBrDZRJvHIbsJtNNYdiKmIc3hYWVjDDNYVZixFdeCuJqiXDrlzeBGhwzDTiXniwRJd8KlMIpxBVMxly5f+KlRhdQxNYzXFqFGNZTrDW4JiU4y1c8GBesoOCsS04XF\/wCFVHGe85yIoKUULVOg8cuDnk1fOtJlVzBPQtPeZV8QR0bD24DLKh0UqfEBEoMrBAi2Kt2ypjmEDmk7S91+ogBXSjHUD3\/wHNJ2l7r9RACulGOoHvxcgEA3ph26jD0URWM6XYn84mtm4c1aJzYWPTzmVXME9C09443XKMWaPUiEqCFYstMJeXjhzQRNhgNekEcmlWODSvPeF1kdOBNHk+3A92rllPjcFZWUlZWVlJZ\/i5cuXxxBEW4KKXG4sJjFcokxDaARZczLspenKVA6oPSXgLWAaQ2PTgHeLMuBNMSv8Z4ia8CMVI7HD50pKsYX\/OZmBwGukFGBiZgzc1cBqZPBZxNdIsvlBRRwW8a4aI4yuJnvOcpUi9yikqFRJs0Z\/QuNGTcGr+TVUeyYpcTVJFHkpLfg4HllaBpecKO0tUssG\/izKyMtbKDdgbeiGdOJmFu1CEMbjJjUv3YrE3lo8yVlyhNnQisEMKarCxRZdqgLILabMygVQAkdjTyPqUgo3YRg6tMuUpVDkwiA9EMSMmWN0WC3xYosu1QFkFtNmZQKoASOxp5H1KQUbsIwdWmXKHk9t02d\/MRc6AACylQDHjEXZXRhisAO+cwi+kgKtZQ6PzD2BRrWUlNzm28LgUAzJkdNYXHyjcMwS+pbQc9YNgq9lZauzNLLYE5Ni1AovkmVSso6hYYAaK9IXjDYCHUvRH1AB3Lcz0a5scKw8AL2b7bZ5TuzDPSvnw0eT7cD3auWes\/xiUSiUcGP\/W4NcS4y+FE0yyXLTWBKQaJaXIwYzlEXlowxKhpHjf8Am\/8ABwF4Wl3KcSo\/8LZhF5Q2ic0mPAccAiZ4UTCMySjgREqCJf8Ai4JcpcxTFSprie85yIoKUULVOg8cuDnk1fOtJlVzBPQtPeZV8QR0bD24DLKh0UqfEBEoMrBAi2Kt2ypjmEDmk7S91+ogBXSjHUD3\/wABzSdpe6\/UQArpRjqB78XIBAN6Yduow9FEVjOl2J\/OJrZuHNWic2Fj085lVzBPQtPeON1yjFmj1IhKghWLLTCXl44c0ETYYDXpBHJpVjg0rz3hdZHTgTR5PtwPdq5ZmHVmjRTZ\/wD3kksghsshggogjgooosssgihhhhjijgsossEIIIEEEkMkkoskkskkskkkksgoAO99k732f5QQYo7P2Rb\/ANAK66K+++OiwgA07njFdZV8a7\/6aOCyyiiQiyiiKSQiyyCCOKyKKZeblutNTzj3nOUqRe5RSVCok2aM\/oXGjJuDV\/JqqPZMUuJqkijyUlvwcDyytA0vOFHaWqWWDfxZlZGWtlBuwNvRDOnEzC3ahCGNxkxqX7sViby0eZKy5QmzoRWCGFNVhYosu1QFkFtNmZQKoASOxp5H1KQUbsIwdWmXKUqhyYRAeiGJGTLG6LBb4sUWXaoCyC2mzMoFUAJHY08j6lIKN2EYOrTLlDye26bO\/mIudAABZSoBjxiLsrowxWAHfOYRfSQFWsodH5h7Ao1rKSm5zbeFwKAZkyOmsLj5RuGYJfUtoOesGwVeystXZmllsCcmxagUXyTKpWUdQsMANFekLxhsBDqXoj6gA7luZ6Nc2OFYeAF7N9ts8p3ZhnpXz4aPJ9uB7tXLMzr7sat83OdtZ7az21nvrPfWe+s9tZ76z31nvrPfWe+s99Z7az21mn4TT8Zp+E0\/CafhNPxmn4TX8Jr+E1\/Ca\/hNPwmn4TT8Jp+E1\/Ca\/jNfxmn4zT8Zp+M0\/GafjNfxmv4zX8Zr+M1\/Ga\/jNfxmv4zT8Zp+M0\/GafjNPzmn5zT85p+c0\/OafnNPzmn5yj85C\/8AOafhKfwmv4yH8Jp+E91J7Kz31mn5zT85sDZc13fLkTkatXNre5T85p+c0\/OKfjFPziv5xX84r+cV\/OKfnNPzmv5xX85r+c0\/OafnNPzmn5zT85p+c0\/GafnNPzmn4zT85p+c0\/OafnNPzmv5zX85r+c1\/Oa\/nNfzmv5z0QN7v+HKPec5EUFKKFqnQeOXBzyavnWkyq5gnoWnvMq+II6Nh7cBllQ6KVPiAiUGVggRbFW7ZUxzCBzSdpe6\/UQArpRjqB7\/AOA5pO0vdfqIAV0ox1A9+LkAgG9MO3UYeiiKxnS7E\/nE1s3DmrRObCx6ecyq5gnoWnvHG65RizR6kQlQQrFlphLy8cOaCJsMBr0g2MwqhOKBZzcUZYNNPopH+8NHk+3A92rlnrv\/AHw7LoXP00\/TT9NP00\/bT9tP00\/TT9NP00\/TT9tP20\/bT9tP20\/bT99P30\/fT99P10\/XT9tP20\/fT99P30\/fT99P30\/fT99P30\/fT99P30\/fT99P30\/fT99wBWgdEU\/6tP20f+mn7aH\/AE0\/VT9FH\/oosuM9DL1PscGP+KlSuFSv\/KpX\/lUqVw9JJ7znKVIvcopKhUSbNGf0LjRk3Bq\/k1VHsmKXE1SRR5KS34OB5ZWgaXnCjtLVLLBv4sysjLWyg3YG3ohnTiZhbtQhDG4yY1L92KxN5aPMlZcoTZ0IrBDCmqwsUWXaoCyC2mzMoFUAJHY08j6lIKN2EYOrTLlKVQ5MIgPRDEjJljdFgt8WKLLtUBZBbTZmUCqAEjsaeR9SkFG7CMHVplyh5PbdNnfzEXOgAAspUAx4xF2V0YYrADvnMIvpICrWUOj8w9gUa1lJTc5tvC4FAMyZHTWFx8o3DMEvqW0HPWDYKvZWWrszSy2BOTYtQKL5JlUrKOoWGAGivSF4w2Ah1L0R9QAdy3M9GubMQ4DmNhja6TwzPQvnw0eT7cD3auWeug5sS+yoNp+9uOaRI3JbmxQPFzNiLRZXTmRypyfG67YQBCyxR42wQxbFFlOtsSiUW7T+kMbm7YN4XK\/LHN1YQDFoGPG5hCbzpfnbCisFpdSudRQ+7TQ08WEhQAnJtlstlstlstlstlstlstlstlstlstlstlstlstlstlstlstlstlstlstlstlstlvDu3OYgYKODW8NanH3wvNb\/wCNdYTttf8AEuy6ODKwBaYTm95XZgDV7L0hVGLvj5VHCmBN77JRDojl1\/sfta+R0qVLbahpGcVK7N7Wc7XGHVxG8VImWu05JOVg6sD5PWMHkw18JWeEaesrE0oxGtYvi5quBqOA1nnsjOWwda5PG5cBRR0svt+QspvTHViGhXnBC26bj5mINk4XtfquVKlf69JJ7znIigpRQtU6Dxy4OeTV860mVXME9C095lXxBHRsPbgMsqHRSp8QESgysECLYq3bKmOYQOaTtL3X6iAFdKMdQPf\/AAHNJ2l7r9RACulGOoHvxcgEA3ph26jD0URWM6XYn84mtm4c1aJzYWPTzmVXME9C09443XKMWaPUiEqCFYstMJeXjhzQRNhgNekGxmFUJxQLObijLBpp9FI\/3ho8n24Hu1cs9dF80sAh11JlY81BWdFeEBLe\/QYBiZ6OSYBewK3+RZ+EVJfkXWVmwjArnqnMw6uHK9dyN6oCgFfOzylBFulO58xYzMcaOSliwOCNChvVOZsQAhlu0sSVpCgHNvdeEyucrDHbXE3DiZa1brX\/AOUazu3OegOCm++2cPlMhptvODXbS4LSmEHGP58QsnpHJzGWb6uM\/wAz\/ePbuceIuy6OI+0WVRdkyb3ba1kXEWiz71tMZAFqqU3p\/YLSyKdKMm2kH8utdcmJX4DSzT+xwGiy79xkQtoAxtQteUB7qr1CnrGWg6UXfOOjMw3LNVdI5bo\/kJhgtaXI1lJ2tDcaVdusqr3S+5TWOb2851Ap5XX1\/lBTORqctesqRUuRW9G9f7DUpPBHT0qGaP0nle2nnKXaCgQDOFiG8cmGzqEbrZoXnzjpBAO4z\/sT3nOUqRe5RSVCok2aM\/oXGjJuDV\/JqqPZMUuJqkijyUlvwcDyytA0vOFHaWqWWDfxZlZGWtlBuwNvRDOnEzC3ahCGNxkxqX7sViby0eZKy5QmzoRWCGFNVhYosu1QFkFtNmZQKoASOxp5H1KQUbsIwdWmXKUqhyYRAeiGJGTLG6LBb4sUWXaoCyC2mzMoFUAJHY08j6lIKN2EYOrTLlDye26bO\/mIudAABZSoBjxiLsrowxWAHfOYRfSQFWsodH5h7Ao1rKSm5zbeFwKAZkyOmsLj5RuGYJfUtoOesGwVeystXZmllsCcmxagUXyTKpWUdQsMANFekLxhsBDqXoj6gA7luZ6Nc2YhwHMbDG10nhmehfPho8n24Hu1cs9Z\/wC9aOxvPDu\/\/wBTt\/8AU7P\/AFOz\/wBTs\/8AU7P\/AFOz\/wBTs\/8AU7P\/AFOz\/wBTs\/8AU7P\/AFOz\/wBTs\/8AU7v\/AFO7\/wBTu\/8AU7P\/AFOz\/wBTs\/8AU7P\/AFOz\/wBTs\/8AU7P\/AFOz\/wBTs\/8AU7P\/AFOz\/wBTs\/8AU7P\/AFOz\/wBTs\/8AU7P\/AFOz\/wBTs\/8AU7P\/AFOz\/wBTs\/8AU7P\/AFOz\/wBTs\/8AURtRW+cHigq7qdP28J0P9fU6Pt4To+3hOj7eE6Xt4Tp\/6+o1YI9uUd3VW+f+Bdl0cXif6dVE1inPP+l7d5qvaIEU1XK\/\/GJ7znIigpRQtU6Dxy4OeTV860mVXME9C095lXxBHRsPbgMsqHRSp8QESgysECLYq3bKmOYQOaTtL3X6iAFdKMdQPf8AwHNJ2l7r9RACulGOoHvxcgEA3ph26jD0URWM6XYn84mtm4c1aJzYWPTzmVXME9C09443XKMWaPUiEqCFYstMJeXjhzQRNhgNekGxmFUJxQLObijLBpp9FI\/3ho8n24Hu1csvTLDRpjd\/\/h4IIIIIIIIIIMMMsEkkkkkksssskkkkEsEkkEEEkEEEEkkkkkkkkkkkkEkkkkkMskskkogimfE4Ol8zDitwMGiO\/GLJ\/wDHIopLKKLKI\/8ApySSSSQAAASSQSSSSQAAAAAAAA8fPB0u9F5x7znKVIvcopKhUSbNGf0LjRk3Bq\/k1VHsmKXE1SRR5KS34OB5ZWgaXnCjtLVLLBv4sysjLWyg3YG3ohnTiZhbtQhDG4yY1L92KxN5aPMlZcoTZ0IrBDCmqwsUWXaoCyC2mzMoFUAJHY08j6lIKN2EYOrTLlKVQ5MIgPRDEjJljdFgt8WKLLtUBZBbTZmUCqAEjsaeR9SkFG7CMHVplyh5PbdNnfzEXOgAAspUAx4xF2V0YYrADvnMIvpICrWUOj8w9gUa1lJTc5tvC4FAMyZHTWFx8o3DMEvqW0HPWDYKvZWWrszSy2BOTYtQKL5JlUrKOoWGAGivSF4w2Ah1L0R9QAdy3M9GubMQ4DmNhja6TwzPQvnw0eT7cD3auWN0N+bm6S3L3+pbl7\/Uty9\/qW5e\/wBS3L3+pbl7\/Uty9\/qW5e\/1Lcvf6luXv9S3L3+pbl7\/AFLcvf6luXv9S3L3+pbl7\/Uty9\/qW5e\/1Lcvf6luXv8AUty9\/qW5e\/1Lcvf6luXv9S3L3+pbl7\/Uty9\/qW5e\/wBS3L3+pbl7\/Uty9\/qW5e\/1Lcvf6luXv9S3L3+pbl7\/AFLcvf6luXv9S3L3+pbl7\/Uty9\/qW5e\/1Lcvf6luXv8AUty9\/qW5e\/1Lcvf6luXv9S3L3+pbl7\/Uty9\/qW5e\/wBS3L3+pbl7\/Uty9\/qW5e\/1Lcvf6luXv9S3L3+pbl7\/AFLcvf6luXv9S3L3+pbl7\/Uty9\/qW5e\/1Lcvf6luXv8AUty9\/qW5e\/1Lcvf6luXv9S3L3+pbl7\/Uty9\/qW5e\/wBS3L3+pbl7\/Uty9\/qW5e\/1Lcvf6luXv9S3L3+pbl7\/AFLcvf6luXv9S3L3+pbl7\/Uty9\/qW5e\/1Lcvf6luXv8AUty9\/qW5e\/1Lcvf6luXv9S3L3+pbl7\/Uty9\/qW5e\/wBS3L3+pbl7\/Uty9\/qW5e\/1Lcvf6luXv9S3L3+pbl7\/AFBuHvOciKClFC1ToPHLg55NXzrSZVcwT0LT3mVfEEdGw9uAyyodFKnxARKDKwQItirdsqY5hA5pO0vdfqIAV0ox1A9\/8BzSdpe6\/UQArpRjqB78XIBAN6Yduow9FEVjOl2J\/OJrZuHNWic2Fj085lVzBPQtPeON1yjFmj1IhKghWLLTCXl44c0ETYYDXpC2G45Gy5t5dNAs6jhNHk+3A92rlgU2RvyUi25DvG4I3IWQ+i5jiRHTE00hpLsRTnghaSDSdTY07kQ5w8Ijv\/k5n8J+RPxpXP8A5OunNU8pXBUqV1gdYiBwITSC6fyj\/wAaHPR56G4o8mUmsxwqVK42SkuXMSpmXxqJ\/gl7S\/KMAgG0tylHLi0iEo8CsrAmJiAf4FJRK4hWU4lJSU\/8gAagPec5SpF7lFJUKiTZoz+hcaMm4NX8mqo9kxS4mqSKPJSW\/BwPLK0DS84UdpapZYN\/FmVkZa2UG7A29EM6cTMLdqEIY3GTGpfuxWJvLR5krLlCbOhFYIYU1WFiiy7VAWQW02ZlAqgBI7GnkfUpBRuwjB1aZcpSqHJhEB6IYkZMsbosFvixRZdqgLILabMygVQAkdjTyPqUgo3YRg6tMuUPJ7bps7+Yi50AAFlKgGPGIuyujDFYAd85hF9JAVayh0fmHsCjWspKbnNt4XAoBmTI6awuPlG4Zgl9S2g56wbBV7Ky1dmaWWwJybFqBRfJMqlZR1CwwA0V6QvGGwEOpeiPqADuW5no1zZcMaArKU0N4E1LznbyMcNHk+3A92rlnqPc4X6XyddCUVOoJcecDw5beZ56XANyTFmjU2iFwEmDbXxBf5j2M17Gr0s1L14Y+omV7tjAc+UUvQTOvvGQKOrS0vNQxWWSuuhcw9VQJRyImRJoFNeM5EK8CAq0V4S+tFeEE1UsSESrCasZi9f2X4lpnx7fuPWgueYaKaMXpc0Bd4vzCYVTmFDaINCibI+cfuunZNiH8mai\/nBgXVTZGbIwTSY\/8Bc16zqn9nXP7Ot6xaMuRU8KeBPDnhSjlMHGBI2tQERpPDiWagTabWeFUenLuUN+pg2mfaYdp1D+w6UQ1l0TwVx4K6jDcGaIMdQMarBlFoGXjQ8jgYzEqYl8KeUzylRUVySo7Lxj3nORFBSihap0Hjlwc8mr51pMquYJ6Fp7zKviCOjYe3AZZUOilT4gIlBlYIEWxVu2VMcwgc0naXuv1EAK6UY6ge\/+A5pO0vdfqIAV0ox1A9+LkAgG9MO3UYeiiKxnS7E\/nE1s3DmrRObCx6ecyq5gnoWnvHG65RizR6kQlQQrFlphLy8cOaCJsMBr0hbDccjZc28umgWdRwmjyfbge7Vyz1HucAroHX4l1Nyy2U57xMAmGNyW8ue6901GpaGtGnrLHS00CoYOz13nqiLViXx6L9Ya8Ymc6UHrXS5hLTYdDxgLGqrTgO9JevL5+MwtDU6uHgVZZp8EPYmjgOePnwBAvdl9UUvOUcX35cD2oexNnjKUPSAoxKcpfSUO0oNiMnJjGqzDhUdZqxR\/6f8A2foQOpeLCd\/FwSXbEAGMq1g6rhjKICeUSbGbviSc0AqiKHNHeTmpqFRdS4Fw4bxF7x8t6M5iNWIWZgQMscmbgzVphdGc0zoZyhPBi+2W7Ywkx0p0+J6OXK9BFdkeWm1I8rEaiVSrQ8rj3nOUqRe5RSVCok2aM\/oXGjJuDV\/JqqPZMUuJqkijyUlvwcDyytA0vOFHaWqWWDfxZlZGWtlBuwNvRDOnEzC3ahCGNxkxqX7sViby0eZKy5QmzoRWCGFNVhYosu1QFkFtNmZQKoASOxp5H1KQUbsIwdWmXKUqhyYRAeiGJGTLG6LBb4sUWXaoCyC2mzMoFUAJHY08j6lIKN2EYOrTLlDye26bO\/mIudAABZSoBjxiLsrowxWAHfOYRfSQFWsodH5h7Ao1rKSm5zbeFwKAZkyOmsLj5RuGYJfUtoOesGwVeystXZmllsCcmxagUXyTKpWUdQsMANFekLxhsBDqXoj6gA7luZ6Nc2XDGgKylNDeBNS8528jHDR5PtwPdq5Z6j3OCyIZRXNs\/kbXbGNCNTLFFypdsutMqxCcppM1sbQ3dBT5xuFa6X8kdgsMtdbxjgjk8P8ASCg7nJn+y4cVq5MrohixhtpL+5Rt5cAsCuJsZ1wRrwS1Odxi7JQsXKGG0NAlzCQrgSyu8qmEehrBhFNkRtpALVcLqJsG0LOJdpvcpAVpKGhiYtONTSXLf8K41GUsqYhxdqzkRfml+eUR4ouWhvHiTVOO9zeljOnOlHkortnSEB2nQnQR6CVnowIKDpB5JiuGjMpyoDE6QIhDjf8Ai+NTEonsR7znIigpRQtU6Dxy4OeTV860mVXME9C095lXxBHRsPbgMsqHRSp8QESgysECLYq3bKmOYQOaTtL3X6iAFdKMdQPf\/Ac0naXuv1EAK6UY6ge\/FyAQDemHbqMPRRFYzpdifzia2bhzVonNhY9POZVcwT0LT3jjdcoxZo9SISoIViy0wl5eOHNBE2GA16QthuORsubeXTQLOo4TR5PtwPdq5YKWLzubqynM\/pKcz+kpzP6TsslOZ\/SU5n9J4H9JTmf0nUP6SnM\/pKcz+kpzP6TwP6SnP1JTmf0lOZ\/SU5n9JTmf0lOZ\/SU5n9JTmf0lOZ\/SU5n9JTmf0lOZ\/SU5n9JTmf0lOZ\/SU5n9JTmf0lOZ\/SU5n9JTp\/SU5n9JTmf0lOZ\/SU5n9J1PUnUP6SnM\/pKcz+kpzP6SnP1J2WSnM\/pKcz+kpzP6TwP6TxH9JTmf0niP6SnM\/pOof0lOZ\/SdT1J2WSnM\/pPA\/pOyyeB\/SU5n9JTp\/SWcz+k8D+k8R\/SdlkOYf0ndZPA\/pOyyU5n9JTmf0la19SeB\/SdlkpzP6QG9f0lnM\/pKcz+kBzP6SnP1JTmf0l9H9IpzP6Tqr+kvo\/pOyyeB\/SX0f0nk\/pO2yU5n9JTmf0lOZ\/SdQ\/pOp6ks5n9JTmf0lOZ\/SU5n9J4H9J4H9J4j+kpzP6SnM\/pKc\/Ungf0lnM\/pAXr6kRkFAmpvX1CX0BupEwAgaoCuR\/FdMSgLqHNtC8g2i5863RY1vVwJT0RFHIpafHWKrIIZXc6LcAitiIHzwxMOrIGRS0Y3lN1QaBe7HWbGH0PDnAFkFcFPg2yxhEsVzsC9plK5KsGmlcByT2lfL112G7nnOkNkLEEIFpavGGCRTfnmOUyxhEsVzsC9plK5KsGmlcByT2lfL112G7nnOkPFBRjuDPXIQetul4uqlfS6KygcizmWgL2uVWuYDpXvHEXre9G6cKj5bQJp2BRnV44zA5Yyti7c+OsSih1TVNG\/NioWUOBVhYZjKxwDfLjHxoqoaxSwVQaJcOie0pgoA5wNif1htdM5ztQfE1+LmHFvLwMQUoddM8ri4CqDpy4HjlpjhTA0Q\/8A8HHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDv\/AEHDhw4cOHDhw4cOwXUmR0PSf\/\/aAAwDAQACAAMAAAAQzwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww2tWyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyl98rCPymxrmz99SeUlj3DNg5G5o5xlbvNnYlhBH4zPM4EMLzY4Zf+keckvw8MYUkIE0aIAAAAAAMd+C63ZJuq\/9lJZuR\/Oc4hbSLDMckbMwpzjT\/tN6HKe+CejTgr+B7DmH\/JEcMIMMsEMMMAAAAAYxK4cjXbjb5wth0tskxS\/NPhfzgL18Dqe6E\/PEN0sDa9pPnhARJGuzBWlL8MIEg8MVtYCIEccoAF9xebSL7jwppQHle4y6bBELIcJOZw48TwkbzB2dkV3D7MLKArZsb+Qccr8cIEg8MVtICIEcQQgQAMedu+fec+vvuOetOe\/tMt++cMv8fP8A3LHzbXLjP7\/zfr\/vjjvrbPDLQfHCBIPDFbSAiBHEMCAjQCNQA+IVN+radizEq3iTkF8z2NA+ZygIu3NFn72X4PygxzOIb8hrqMpTRp3oaCmRkS3XxDKCwOmFc8uScp8JdXnxgydDiNDs0FYqwg9A63BcnlH7HcG9PrA9L19DE6qL+jT2VETTRDG42ZTQ0S1fvD2G14LL8GhYMswssLuMA8MM13Qsgvv1brNscES8hcs\/\/OvessfTggY\/HCBIPDFbSAiBHEIKFaPOPcfRCgyfLBRyyRTzw\/SQXcZhTTcQEABc14iELn3t\/sudfc\/\/APb7boPxwgSDwxW0gIgRxCChWs77tBa77Aj2kluWX+s\/83m1n2qXb\/bDqfBnk14JI2CHiy5v8Es0aLZCDxwgSDwxW0gIgRxCCBX9lEB3zGLrGHRJzSqhQj0G3naIb4K7bH384ojT\/jV9M2bQudEj54IaJYPxwgSDwxW0gIgRxCCBX6Xynjy6\/ChQzi0MHYR8fqankpv98BDWkIRf02zBkbYdc5SFEFQQKp\/iPxwgSDwxW0gIgRxCCBXhVXghYb5FQhbwvTH08XjWT\/BVd9hpaL3OlZNgWGfCMAQZXcreJZ6m8qPxwgSDwxW0gIgRxCShXtSmER63GsMZXyRR1UdrflVcREukIP8AhOPHQRLgbMPwapgVMPP5Ahi+Jr8cIEg8MVtICIEcQkoVtU3AA0THECxxGTY2acsefLE8zbnvBcWIfbkT\/wAg5K4+hXjCAH5XuW\/WSPHCBIPDFbSAiBHEJKFfn19wx1xKGBMPJQ8PJXO2EdDraLzD8efnWTPddLNC7wb3g9fkNa29urIfHCBIPDFbSAiBHEIAFfCrShZ0JFjngszJN64+NFPWUhiCg\/qi8qD201fjlDpmlYtYDqsJ8PGeIvHCBIPDFbSAiBHEIAFapiPGx1pjScRXRSns94z3ikpgau0NXQVEcw3\/AEejf4fRuwq6RCqavgvwLxwgSDwxW0gIgRxCABX644IZvfmXkejk1lO7uswTKDZESogOwmwo\/CiAdO6pwoa4LnKRHPWRmMHxwgSDwxW0gIgRxCSBX4DxJzfwF3RYL7iHBlqyzoMcqc26hzC2cxg1JgBrA1UPq4qN4gsZWdZinxwgSDwxW0gIgRxCSBX7z+9lnH0QyAwRjhgzlXAhEUQt4ZyanmJMcW0WnVb\/AMWt4UsMqSR9PLbr8cIEg8MVtICIEcQkgV4pxOuO2Cg3bbAyq2Fd18tMklWxkZ+A8OmUpoUloUy+FLHIfpt8ZoWwZr8cIEg8MVtICIEcQkAVo5mak8sonvn3ESGCdiC4YMoWFQxmtlWp0xi4nmKsdu7aqfW1NAfbdv8AIPHCBIPDFbSAiBHEJAFfvUYZKFHMAGJD3z30160x\/ri1GDDD2wOp7x9k+k++ywzkAqMp780\/234PHCBIPDFbSAiBHEJAFbylvqBPJAFMDDD9fAAPMENPPOIAAAFExeQefE0i9vwp9PDDDDAAMPPMAPHCBIPDFbSAiBHEMIFas6AABKkGMMMMMMMMMMMMMMMMMMMMOEAPOG8MjKSdhojNtpdO2HKDDCKPHCBIPDFbSAiBHEMIFfgBCEMLEBPLDGMDGMPBHAAHLDDDBPEi9tsrriGJ2siqoDDLDHDDMADPKPHCBIPDFbSAiBHEMIFe9MMPPMMPNNOMOPMMNPOMOPOPOOOefs9ssuu\/dN988\/8AXjjTjzzjjDzyjxwgSDwxW0gIgRxDShX7xyEGq8HIY1Pbh+Tt2vfsihTzRQhjAA8s9svDyDoBCRjRhgiiRQQQCCDxwgSDwxW0gIgRxDShX\/GRNLkpGBACwMACPPMtUGD5Jbqpo4ZoLr5K5LJo7a66Z7po7Zrroo5Y\/JA4aDoBSMQIAZ5TeZ1z\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP4\/\/8QAKhEAAgEDAgUFAQEBAQEAAAAAAREAITFRQWEQcYGR8KGxwdHhIPEwQGD\/2gAIAQMBAT8QwFAKACM5MZyYzkxnJjOTGcmM5MZyYzkxnJjOTGcmM5MZyYzkxnJjOTGcmM5MZyYzkxnJjOTGcmM5MZyYzkxnJjOTGcmM5MZyYzkxnJjOTGcmM5MZyYzkxnJjOTGcmM5MZyYzkxnJjOTGcmM5MZyYzkxnJjOTGcmM5MZyYzkxnJjOTGcmM5MZyYzkxnJjOTGcmM5MZyYzkxnJgQkrUqYDWJBAb9Q+sILPOI4MRwYjgxHBiODEcGI4MRwYjgxHBiODEcGI4MRwYjgxHBiODEcGI4MRwYjgxHBiODEcGI4MRwYjgxHBiODEcGI4MRwYjgxHBiODEcGI4MRwYjgxHBiODEcGI4MRwYjgxHBiODEcGI4MRwYjgxHBiODEcGI4MRwYjgxHBiODEcGI4MRwYjgxHBiODEcGI4MRwYjgxHBhkCFl2hEhO7gKgQPuSw\/8A4GGKKLgoooR\/wAlwHFw\/wDktwip2JUV9vifnqvuaHzWKfvoHM7cNOhh+\/j\/AIXeCstP8nHwuAhAXJiBsYAJhFYAgk6P2gmcWSEXUGEqsBG6Z5xIQvgGZBdMQ+J9QipL+RFCODjj\/lRRRRRcF\/Kih4W\/\/Fd4Ky0wxHwRi42ucMLAg1Ey4wmpnufafBOVLAVBrKBA1XrWEJCsICHw4XckJyTSA9qnSGAJu0EGaIowA1AzflFYDvABEUq9ICipGQBQQGSGUDVEEMIAKhcoAVIGoFZwAJAQbPvADBUFVpWCoBDDa8BwAAAyhCAskNoIEgE36RMgnlCAHTlAFQgxoBbSKKKKLiYpZhudiVM7fE\/PVfc0PT3h+\/yfvxDrtAmH5WC1b6zO3h4advmZ2\/Puaeaw69V3UxuPnhd4Ky08TgPFQKOcIg0OkJOphWZEMzAqtX60lx6TaPaUiLqTmnwiEoADtAAYqjwu5ISapvMv+EADAOKk9EHjpPNylnX8QlGiVOEBNcfsA0kKX24NRGBe3GEoaKIAHxpEIN\/8QgYncj4laAWJ83xAldAuICFW6Rri1F\/KiiilqG52JUz17Tx1X3ND094fv8n78fsOu0CYcFq31mdvDw07fMz09xNJnaH6ekzNHhWGoLVnNPBTwU8FOec85pzwmbmc\/CX+EKbwDFvRF\/hCZu0BAsJ0i\/yl7O9J4KaI9IfATRHpLJftDG3DyhSJNZ2iCqrQGF64+sZThThSgGnChgQu2gBwIcLhOkAgQFB2jDFu0SL2RBVW0JEI2bQGKhOkX+HC554Kc055zzmnNOac0BBQ3OxKmevaeOq+5oenvD9\/k\/fj9h12gTDgtW+szt4eGnb5menuJpM7Q\/TonDcjBImjwrLvnCDmHcYdx7xhqe83D3m8e83j3gyGPtGJEVDJFTS0FApCQOhOxn+0gkJuqm+sBwACSCQsX6ygCkJDc7QDJjBpKMBJIYzTZWAiUV2DCEgECbG68oDCQCRkeUXSVy7xpNYDMI7htodbQgIBC7UNYQoQ2QGlTHaEACSEoJrQ7wFKBInQEykjQWyRyEDtd5TnCD6YI7gUHUG0INBc6vKE0OxDSg1rKACSDsSLxIEIoBV5WVQ6IuAAGtBgW5yrivzNw4SVA6qjvBSemYAxCMoxNxGsW1pCyhOIAC979IJCKABVQ8puHvNwzcMbJjZMbJjZMbJjyMeRjyMIgjqKkJSBpqs6reA2X0BW5PWAnv2LXztEuuhkjvAg1+oQHWcOqg1jR6bufOsS69xc6ddpcorJYW\/2Ejr6CpvWEpIuyqksQFdvJhqKVXXHWAlfQqNyMfRu5gZS1qZUesSWTCt4VgvzQmHgv5JggIZc1sJ0gBrKF2qftCA9N1liagUIUNkAggTah9c0d4EADBwp8g3G4eB0DQcoG0DRZswrhCOVWCtauIaOTHkkNSuGekIElAEQbjcHqqQ25ld+T1oa2j9A5TuAIvKgoE7js1hhBEbReVRCYLcwUGfRyq8gIAF2YVRr+QVUuNcSVTvF4KIqHQw8wU14ZDApasXHOFEBwQkFgQzCBMYlyP8AE6xFwpBgBWEm5maohH8grExYUQHGQLGlqECAWU8aV1AHWqgHMFQktYNid4V\/QJnMnncwDFzIRWgjrrygnLas3BuYAvQpU3K0YhVSgALXA1mhXzROdBeukYBAFp1c+D\/5AChq4WnOVRFEoa1Sdr0NV9ymdvRmHu+jpDT39G0NXT2QNHtswKq\/WiXtPZpef46UeXo3in7bb7lL42uJf09kPW8ME+8AACFBLvBWZ5xHERxGxGxEcRHEbEJYjYm1NqbU2ptRbguUeVNqbUdaXJI1j4JlAVTpCQYQwRQgjBhOWQS5sTYmwYBkEAgioIhQgISyTUkzdTfTfTfQB1RxcSrtAQqwkQKWXhjasHl1MBq5iCRAAgJYHKGaSrYzYmxNibE2ptTam1NiNiNiNiWQ3OxKmevaeOq+5oenvD9\/k\/fj9h12gTDgtW+szt4eGnb5menuJpM7Q\/K9Cffhd4Kxp8+F40aNGjxo0aPGjYjYjYjYjYjYjYjYjYjYjRsRsR8R8R8R8RsRsRsTagLEJwPrADUwlAc0hBpAWI+I+I2I2I2I2I2I2I8ePHhMOG52JUz17Tx1X3ND094fv8n78fsOu0CYcFq31mdvDw07fMz09xNJnaH5XoT78LvBWHXn\/Kiii4qKKKKKKKKKKKLgoooov4EQZo4BQh8TOFRRRRRRRcFFwshudiVM9e08dV9zQ9PeH7\/J+\/H7DrtAmHBat9Znbw8NO3zM9PcTSZ2h+nROCXeCsOvP+XB\/Siiiiiii\/pRRRRRRsCRwmKCEwQxwCBFFFFFFFF\/NkNzsSpnr2njqvuaHp7w\/f5P34\/YddoEw4LVvrM7eHhp2+Znp7iaTO0P06JwS7wVmefFQiLgQgBWA0fKFCAa3aLipnL\/A8wjBJCpG5rxfEZDCAAjBqCP4UUUIHEiQLUVYSIBuah04BWgMm1YUUQToBpACAWFquIEg9yAPtAYgAzKGCRg6xZjTK0chfgPt\/CgKhDiggyBYncoIouChCIRQWhudiVM9e08dV9zQ9PeH7\/J+\/H7DrtAmHBat9Znbw8NO3zM9PcTSZ2h+nROCXeCs1PP+FFFDwYVilG7hCRUBTBLKYAkBCEtWmhNL15QCBDNo6qaw3BklsLbXMBEh00MFg8H\/AGHX0sMhxWUoUJBgYFAGpiIOgowSUGm4E7wwyLAUDew7wScSx7gGKw0k6XxAUXF2orlr4lWBTSQ1rBJG0gEE43J94SpYW0g5Bwo6aBmbl6CCGWPUAF0xrGhQKqKANRKGTrEGBHtLNFQnXaCgFAMKHqUNqSm5MRmgakCaWXpCgkqgGWPqXB0Ss6AYaw9OCGAMEElkOCJSwQFi8CBbUr8EPeEVihBVSxoR8wi1EJAqAfQ9YAgx5y9CGqjmQNAXTqdEFCZRjZYtqelqQgcV\/qVcQ6aQjAEPa5tFiAtFxkaB5B3UKDxJYzUBdMayryIOBhX5IoBxGgLhEAiRDc7EqZ69p46r7mh6e8P3+T9+P2HXaBMOC1b6zO3h4advmZ6e4mkztPx6E8LvBWC\/N\/Sih0IZT2VXKIKogUNhUxYa1qL5NTgrRyGg3gEeoJYtsUoCtNq7GIXwbZyE3cBGxIbi7GNI2aNUT7QMEUA51NP9hA4Kkkb4cBO0oUTAQrWkkEB85c0BhAKeHCmHMbZF5UQKcuPQaXhVZVa0u5oSsFAhvYMvPFlQORTYhgkU7ws0yrW4wNZalsNRzoonEFnQKhZEFgOSBm1rtAUoMWgACydqSoqUakU+cGMEDepvvGi8n7AT0YI1IfNHKqLHvCCl6IASXYOsYQADNBFvlA1klSHX3gMAAQWBQul6XgQIjeSthac4UIAVj3hHgPSQEOwaiGrAgRpTBZj4BxCQxymEyTDc7EqZ69p46r7mh6e8P3+T9+P2HXaBMOC1b6zO3h4advmZ6e4mkztPx6E8LvBWC\/NF\/Y6s4Y5LQssPYCNP2GgSZaCwtZVEJe+lYSnYlkW7RCC4EqjNCFRqEaWNWPqGNQzyCMGVpANgjIhcPnAp7KAQCTT1HWs3CywgotRSkBbxAiCi1FIk\/JKDoEC6wxHyAAu0Du82kaitHeA0G31Xh+KWjA5qUfntwDn1BEIhp1TuwaQEEQBQy6gRSkJFWYTeyMCUHU7wCWBhuNi6aQAcm4aIekOlVKag1ailIkCKImEDppHsNQLG0USQ9gOwg6wpG4EwIDaw+4RpgJIgDUaUVIt1QChi5pEk0JOsZBhkyJWGgFb0Sgky0pJHpCg3VTQLdo7px0bbVvrCIa7wQISIRIRxAY4BRCG52JUz17Tx1X3ND094fv8AJ+\/H7DrtAmHBat9Znbw8NO3zM9PcTSZ2n49CeF3grL4UXA\/9h1goQo4KAqXBVGdRHOG44dAwBo8QnRmuZJh6C9JUNtOhhMQRhLKLlARFFaoZAsCC18ikKBIkmgSQTcHrDwiEUhiNAGFziUHqIwKGBqahQhUEwwyK1KwiRI1QoNaiCASNt1W6\/RSsEGrmg1laJ4hJthCAwQ6olQIBBgDDeDaHtEazDEBChuUACyK9P5ArxUkYzwkOAiigEXABgIXL2hudiVM9e08dV9zQ9PeH7\/J+\/H7DrtAmHBat9Znbw8NO3zM9PcTSZ2h+noZ9y7wVl8OPgP8AqQAy3oKy9oKn0B4EG6AAzWMFd3HWFoMQoUOwwCc7ibFaQnA7GXQUa8XQSGliwVMGXMwAE0hkqGioARjoeoBaCrkP1naUfEDoALw0XG\/OkfOAAV2EAbQ+FyC3KHCCyuEPnAALQeYJBNMhRs44mXwKcCrBFPAGFBFwccBnsvaG52JUz17Tx1X3ND094fv8n78fsOu0CYcFq31mdvDw07fMz09xNJnaH6ehn3LvBWXc3\/hwhlQJ6n1geMAdI5NjMojIheciHzCgd1ZbO1LlBQ5EBrAjHngECoFaNFHgk8EiNhaB0XTQNGwWBo3Bz98GXc2kK0DDWQKoRBHOCmFBbIJjqqmGVPGJutAGImvovppUCGPxUA1OsLway\/bdDy4IA7ag\/aEYCVZSmVZdTFWCgXoGhpHlRIytKwAoUChU0BUAZofSKKKEjw25SgpClULQJA4EQ8Daew9obnYlTPXtPHVfc0PT3h+\/yfvx+w67QJhwWrfWZ28PDTt8zPT3E0mdofp6Gfcu8FZdzfwEjUG7I8SLAhNfoYsDp3reGhF0DY7iFckA5DHAJXpEhCRjcDAELVhSq2scyvKahBJOBaEgIIKA4QHNFLSudtJSVVAKlWmoYYXlVkNaKusGEjUQKZI6XYg5AQkNFN51lkXqEprFaiT7v0hBgA9g7JX+mKMmHAAaEDALogKxdgnmUAAwHICh0kJvSEJAnTfpRqJgKSg7itt4ogoAxQNTQagcVDTACFwATozDpGoDkMbVK4hAyAElcHwhMEGJKqhgWY3UrTtAQEh2EwKwoIIIJsFmEhKGEKIHBYjYKRolbm5feNJoiOoQ9hECiB0k3oTCAEFa6gZFELSAsVUOxeITKoA6QfVWDaRAQzRY1Dekq4viaSqE9IZzgloagEhHZRGY5mDAADUgYBDkhWGSMII5JQA5sBjpCTpIULc+ATDAMAcBC5e0NzsSpnr2njqvuaHp7w\/f5P34\/YddoEw4LVvrM7eHhp2+Znp7iaTO0P09CZqeZl3grLub+CAQQQwaEQ+OBUBbTxAiCAalka5mvhdRCPSPkJuK91YLABVEu4EgSqAmqFvaBJbEgVKBYiJfQQNQTIWZ2gOMkEwWWOsWYgN1LrD4qMMq5QAJLGhAIiDuhAAAKgVSU7+kIRGhVakDGTdAC5IxICHUCjCgmg2bBuIBEBF1IqsxkiwJJ5RxZoZaMOB+4Va2bwYLlEtaEWI2UAxBkRjvZ6RgGgIqub0t6RgEsIMIvQVltGxIwgumi1YFWrAtYjNZfvAaQwIy4YQalkBAxyFX9lW4AMzYgGFbaNNCAAt6S1UpX0O0GAijZguqCrrybx4CojE1JaxoVGITPeFVAUyp6yIBYjVFooE+0Lg0qMS+Zbho1UAIaUFqKXxYyRYEk8oAIFiqJNCMTEhri6mBMOsJJCqNCTCgCGzcvaG52JUz17Tx1X3ND094fv8AJ+\/H7DrtAmHBat9Znbw8NO3zM9PcTSZ2h+noTNTzMu8FZdzf+BRRRfyEcKFX+CXAHAcHwPgIDAQAYYRDUQJEKQSIQxwSFwEz6Q3OxKmevaeOq+5oenvD9\/k\/fj9h12gTDgtW+szt4eGnb5menuJpM7Q\/T0Jmp5mXeCsu\/koov+Ig\/twwS5QY444UJ4HH\/A4AwGMvjCcCFxMDSicWBg4VMJZcNzsSpnr2njqvuaHp7w\/f5P34\/YddoEw4LVvrM7eHhp2+Znp7iaTO0P09Hwu8FZf\/AAcccf8AyBh\/kTCeAj4PgPgIYAYov5cpAYIARYBglQhOEpWLPA3OxKmevaeOq+5oenvD9\/k\/fj9h12gTDgtW+szt4eGnb5menuJpM7Q\/T0fC7wVl0OOP\/i44444444444\/5ccMJGPGjRGLgooooouD4HHHHHHHxNzsSpnr2njqvuaHp7w\/f5P34\/YddoEw4LVvrM7eHhp2+Znp7iaTO0P09Hwu8FZdzfwoooooooooooohEIhEIooooooopSIQxmMysrxX9UiEQiiiii4ribnYlTPXtPHVfc0PT3h+\/yfvx+w67QJhwWrfWZ28PDTt8zPT3E0mdoflehPvwu8FZdzTPmP2amDfA\/YNH19P2f4xOXr0\/Zpv8A+swcbEdaHKUfUgb0p6zXpfaPTb3CK2Sx10guR5zg18OjguOvzNAcgE8DYz8f4Z9NQ\/fp\/JudiVM9e08dV9zQ9PeH7\/J+\/H7DrtAmHBat9Znbw8NO3zM9PcTSZ2h+V6E+\/C7wVl3N\/wC+srKysH8KKKVbg\/6m52JUz17Tx1X3ND094fv8n78fsOu0CYcFq31mdvDw07fMz09xNJnaH5XoT78LvBWXc3\/vccccf8OOOP8A7G52JUz17Tx1X3ND094fv8n78fsOu0CYcFq31mdvDw07fMz09xNJnaH6enC7wVl3NABGQSLsDqBFYGFCw1OgNamA3poNuCgsQUQKmAUOcMCJBgGGNSHeEEpHJNSQQw4YAOEArJglVCFngJprBygptu+kBg0K+uFIrWDO8AEv8kYBUwYGCV2MYatkdphQLzL2rqcO7QjXMFVnULw0K\/5qKKKL+FFFF\/2NzsSpnr2njqvuaHp7w\/f5P34\/YddoEw4LVvrM7eHhp2+Znp7iaTO0P09OF3grLuaFIERJgjIgC8FCADVnfWakO+oULmSBoMhZMRZBpShGkroQFAwbiFkwWGIoDqYuGFi1VgVAqakMhVodICAClUuFChQnWgqQe0xxFFQ1cQAqhcKAAgVNBCBOtBcHtAdaC7EyEjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEKZRypTO0Yzj49o6HzWMe8Yp1+P2MVgTFaQGgzGKymYwu37KVrj4jEYrWE+\/ooxLvBWG53f\/E\/\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/wD\/AP8A\/ow\/0Z\/\/xAApEQACAQMCBQUBAQEBAAAAAAABEQAhMUFRYXGBkdHwEKGxweEgMPFA\/9oACAECAQE\/EAAQyGTENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBENBD6I9oq5QA2OkBAAekTURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURNRE1ETURkEAENYATDnLgaBl48Y\/g+q\/kGOOP\/Qweh\/kf5L\/AAHpfgxuq8VMdOpnPyvaZUx5rPyYe0Nit17w3KwnKY83\/wAPaH4l48Y\/lRRSgE6QEwwBGBoUMAI39CAGqh0WY9KJHBEsBWHchwjjBegwghVd54n7KA1f0\/8AZ+jj\/wAb\/wD4reA\/EvHjEXqP48XGAITWsMEBFjnFPg\/M+ycaLUuukrRw4AGSoCYEfKKfIg3+kJIiFErBIBQMmHFkkQxwFC0die0IQQawRM8FCSJcAlVRCQ7iGAiIfeBgwkmsq4nQqAAkgg1XSEYDWh8IRxNftaGwCSSaMwGRVBQoEE0vePkEwBesYDnCDUkRYY4\/Vxx+t+DG6rxUx06mefPaZ80msF+Y+UZjk4c7fvaa8SuAUNn4SbQ3W8x1+ocry\/aZW\/3efnxNfS3gPxLx4x6qKKKKeLjDBYBvAALBQKDofmHBUbwglVL2rPum26yszZWG6bfaEmGSQ3jJSjHpbChYQWfAwmJmCp7kscvn0GXh9wB2cpQbkMUB+QxUX7Pi\/MYA3jE2QFTLjkbD\/qMCt\/1BIA4GKgk9Z9U+JACE2KgIbfAxEQQOv+JlyDG6rxU34dTP377TPmk1gvzHyjMcnDnb97TXiVwChs\/GbKG63mOv1DlcvftMrf7vMPYzJ4FdV7+lp4H4goFOk8HPJzwc8nPJzyc8nC4Ru3gQEPdPJzT8o\/8AvLB75dHW88ZQAKBecIEIvzm7iqDhas8HMgqPJzKKjvjBuPQEACCgbxhRcFl08I7wyrGuKpV1cEQJqG8IfQJAgtVhMQS5G8Cx742fyjCi4KEhXvCAIvzg\/wCueTnk55OeTnk54OeDng54OeDhMu0GN1Xipvw6mfv32mfNJrBfmPlGY5OHO372mvErgFDZ+M2UN1vMdfqHK5e\/aZW\/3eYexmSNiuq94LS08D8Sy4QHQINg6QbXSAH2J\/yJ\/wAif8Cf8CEhXuD55QivRePxEYFGqcVU6gXXMzpRACqAUbGgbmF7wArPADqLOEIwlDTIVecTJNpSEBUgOIE86TzpATJAGQ4dYmAVfCLv0I7NdGirzlmldrTwpADsD6QBBpTe0C0VUOxhDJtOBBDFCFVYmTBqvrCUgxAcEprMNICSAUJRBa0\/5E2HSbDpNodJtDpNodJsDpNgdJsDpNgTYEAESCNLEAw+AQpnSEBW2yaVC5XhHCNhA\/UBCCnsDraFCkOEuGkqLBfETAaKwjcVAbA35ayqEQFLL5gIjdaDbvAEA1YGARyIaDOrDtBQGInYdoQFrRKPMIB0Kw7Q8NU0NHEpAFKC1oNDwPxMcEUAigP8EQAJEbV5WgsDLrCkwJ4OkCCBijmISdJvfMBqno0HuYsoIRtShhCsGtPtBALHTXSAHKwwpwgVUWs8hNwQWKEIVcDcGsshVAW1JQpogaNMiFqCF0LAAcCq\/MhUIGo4wyQJoRlOCuAKOaYwYJhQb2QZg0LLLNIEVhkW0Iim2ilLTXeMMGCBKjoZVyl+YNqyYG2uOMcq5stvBSZCPZBl0FhzwCTX4QgaFShY19zFAl2yMQQAQI0CN\/6fo44\/QiGBREFHZq2VGi\/ajXNveaN05b9pV+1pLvyoG7oy1RlPZd0NXDu37S7qlVWChT9qpNoWrLqlHi5bQ6qjSre0qrLq1TMoPCcpsHVtAfSsJJqalC89ofiMCqNyb03pvTeiazcg1pvTcm5Nybom6JWbDs5uzdhIEUeMAKBDhCUIkEHBm+Jv+m20cESC4BUBAmymymymyheEdYyivALpFHK6QYuVCqBkTzAMQEAzCuyDTTbTfm5Nyb03pvTem9N6EdZUYMbqvFTfh1M\/fvtM+aTWC\/MfKMxycOdv3tNeJXAKGz8ZsobreY6\/UOVy9+0yt\/u8w9jKNY\/AfSs7Ce0PxExwRdYsXWLF1i6xdYusXWLrFgDWLrF1i6xNYmsTWJrE1iaxNYmsTWLrE1i6xdYmsTWbk3puQgMyiFMQhxA0IQCsCcw60TWLrF1iaxNYmsTWJrE1iaxNYmsIRUGN1Xipvw6mfv32mfNJrBfmPlGY5OHO372mvErgFDZ+M2UN1vMdfqHK5e\/aZW\/3eYexlGsfgPpWdhPaH4g\/gPo444444P4Bxxxxxxxxx\/2AYMVSqBAjhMET6RDjjjjjjjjjjl0GN1Xipvw6mfv32mfNJrBfmPlGY5OHO372mvErgFDZ+M2UN1vMdfqHK5e\/aZW\/3eYexmTxEdV6e0Px\/QoR\/g\/Rxxxxxxxxxxx+h+obilo4YBDMxQlf4ATjjjjjl0GN1Xipvw6mfv32mfNJrBfmPlGY5OHO372mvErgFDZ+M2UN1vMdfqHK5e\/aZW\/3eYexmTxEdV6e0PxPp\/Dj9ARBIDAvCBPNv4otkny1gOkVa+pBAzCCCjQj+iYCY\/QQggWs4gAgQDY4POFQAkgJaCCXFxBhQY0IQhkA1VIQAklS+3GNmJdBzIBqF6qOES0JhMQFLUCW0vMRGRoQQesDBjdV4qb8Opn799pnzSawX5j5RmOThzt+9prxK4BQ2fjNlDdbzHX6hyuXv2mVv93mHsZk8RHVentD8THBHHHHHHFChy3V+0bgtokVvDM1OECRtYQlSlG50jQAbIQoGQBB2WqgCARC4fCArN4QsEnjCaII7Qy\/mGDpxBwgAE5MpnoRCDLuiQFSLCARiBpurhxEg5AdYIANasvhBT5EmoewgQAByEukFrDEGh2vcIQpGRFLatxAYUgKEfMQlFASJ5QwECZjoNALQKmwEs6kLCyrYw0Qc3INhaNlnUAsRF6JpANLLgR4oQCNdYWXAw4M3MCDSOz8oSQKCBeoxneEwZ2vkIxE1G+DtrWJagsHwgoAQaKFBlQVLrOGsIscJr3wodSwlgS1QQHC4EAFietYCcYRJiKOg9AWEwDBjdV4qb8Opn799pnzSawX5j5RmOThzt+9prxK4BQ2fjNlDdbzHX6hyuXv2mVv93mHsYbnRfYHv6e0PxLOCOOOP0ccMOuy\/wCpUWB6pQAQNG+5jUKlqkZANpBEndaolAQJmjuIUBFQgbCHUMwqBoaWHEB8C8IeR3EI+AM1xD0FDDWqgumVXlmNo2DLzW0qUPGBquSCK0A24DmIlLYK1uOJweTqSv0gv7cCx3lQKiyKG9qy+YFG9nKMahCZtf5hFU0JtiG7odxaPGoQWrLyUJEv1C+4dQ0ZWxO4sIsEkkA+UNQBK8iCqFeozARIQwhRnMJJa8UwEmSY2GAoBiUXWlKxzBAmCGARCEATAClvn8wY3VeKm\/DqZ+\/faZ80msF+Y+UZjk4c7fvaa8SuAUNn4zZQ3W8x1+ocrl79plb\/AHeYexhudF9ge\/p7Q\/ENuD1f8no88l+sCABIFTeBEA2IAWKntAALRh944JV8VqTSsKMhUEz1gCAAHgtDoI0RcSgAAgXUJEVYDZKbAsiNRvDGigIIhgYMCHZQVBrqnynvJCfmYIizmK6xZAIspaaTekLiOREVRvCKjitDpq3CXTgiKwIAXUJLNhxrGmECQcDEFSBnSMiSL6uKMQHiMwoq0Ko2Gsc1cUTW3WGQANFHIAoEe8K5AKgAsBCEHNq2lBEUxFc+MZcgQhsLQqK6kFEtICIAABb4tWVQAO1oQWWRO2l4QBuIV31IUJDpBAfQuKERKKzDJMd\/mDG6rxU34dTP377TPmk1gvzHyjMcnDnb97TXiVwChs\/GbKG63mOv1DlcvftMrf7vMPYw3Oi+wPf09ofiW8Ef9uOOP+nFZMXNGFBJNakYCsIQqEkALGbkEVUFTQ1qAxRSp3hsE0niUY7DVFiBgAHhqARKMCQdNDWJgSpNmwqy5QwCXINTRzgYGpHVJ0wE4KFVCokmgMNSiNN5G1h9QlQigwts1GUS0oGU\/oRwBAWyUXGKM9XUF3AAdSNQC1fV+hIS7MMEEQ+iaXoEEUUUUU+Z8wY3VeKm\/DqZ+\/faZ80msF+Y+UZjk4c7fvaa8SuAUNn4zZQ3W8x1+ocrl79plb\/d5h7GZOiPyBOwntD8T2novQ\/6gyQFE6gXteGQERcegQgyFmkY2BHEFFYFcP3RgLhoqVhSFXJPGVCB6CuKmUTiFdpWKHrNqBEbgkm8PFkrvsEAHUIgVUeJvCFB2cIQTap1FrSKFk1vW8A0AVAh+QkpOOUsZ4fwdIVYUULwyUxMwgGGkBg9FFFPnfMGN1Xipvw6mfv32mfNJrBfmPlGY5OHO372mvErgFDZ+M2UN1vMdfqHK5e\/aZW\/3eYexmToj8gTsJ7Q\/Et4P8D\/AIjCKlPHtChFKACnTJ4yqgDDchoQYCAxjwZtEJIBC9TFQU0Aott48InAAUJQxmEcAFgKPCtIUKEEnCTjggcbVVgJwaF2hzYqExOvvSBAAUgJF3F4sBADqnV8GLSgJoNsRQtZITreJhBZA0QOQ4R4EmiIKk7CBkZiiyOsE7BULq84XAqBsNnaOOOAx6aXoHoJESuARCYDBFFBQ84MbqvFTfh1M\/fvtM+aTWC\/MfKMxycOdv3tNeJXAKGz8ZsobreY6\/UOVy9+0yt\/u8w9jMnRH5AnYT2h+JbwfwcZRlKxhlGABMlZlUg3AiowAIGhCAHEiA5ABN2DpKJLsscRNURegADLMBDQwRLBYpeEQZouOEMAkmHU4QXIlOqs0SeQUOMSgQguBUwaSSbMAawRZkvQvasDMqMVfaGEtkAW8MkUDrBQEUYH7ihSmAA0iYJVVCTNhMwFAMRYgwbQ07bcYKZTltOlBhCIm99uaKIg4CUDQhwZB1OqWeEpoibuoHujtHlCyzUAOLEBkidSRYQzBjkFSZY1pKYB1Gq3ILpCghkakWL3jy4BqDb1EBAl7CeNDDyjfkHziqiEjmuyFBaG0sOIPpDyQMJoDchQbDGKIxg1RroNwLl9RcLIDb1KFFqMDQOxhCAJoAmxAkEG6Lu9ACBCISp8j5gxuq8VN+HUz9++0z5pNYL8x8ozHJw52\/e014lcAobPxmyhut5jr9Q5XL37TK3+7zD2MyeFOoE7Ce0PxLeD+ASCCCiINAIECqV+MKMwTwg8gllQQkuirptCAJgrECBUQAKVlutA6YrK6ENYWG0JCJ3pSZARNQSAqK9YTi6q2BdawnCS7sgAECFbQmIEbhD8wgGgCgoJxzhhMpKESUrqHKKYVBYG0TSzkQ3wWSS0LEhQVhcDeMI6iE0H1vCIkCKEaSoQKCAVEbjeBUAEAEICrtDqcAsZ\/wAFXebwSVFI4gkjRQQ3QWVpVLWg3gdBSj+QskgPYw1rVCKlehSQAKIYgGExZWWgMGgUzgFmij5U3lRrUwHEi4UJBcrQdYAgIYEcEwkBYYhcEgTpZXaN1W1AoNAQE5bcMEIkMM+R8wY3VeKm\/DqZ+\/faZ80msF+Y+UZjk4c7fvaa8SuAUNn4zZQ3W8x1+ocrl79plb\/d5h7GZPCnUCdhPaH4lnB\/4XH\/AETFAIFIqRRQNBCh9FFF\/JEIgPoAxKsCRBQCsKAOGRQ84MbqvFTfh1M\/fvtM+aTWC\/MfKMxycOdv3tNeJXAKGz8ZsobreY6\/UOVy9+0yt\/u8w9jMnhTqBOwntD8T2kcf+5MBg\/lRQSyKKKD\/AKEQiEQRuNhgeiC9CoQBBQY3VeKm\/DqZ+\/faZ80msF+Y+UZjk4c7fvaa8SuAUNn4zZQ3W8x1+ocrl79plb\/d5h7GZ2X0Hx6e0PxPaRRRRGKKKAf4koCgH+agH8H\/ABIiigKEQlAXoQ9H6DG6rxU34dTP377TPmk1gvzHyjMcnDnb97TXiVwChs\/GbKG63mOv1DlcvftMrf7vMPYzOy+g+PT2h+JZwf6KKKKKKKKKKKL+VF6OOOOOOOOOOOP1UUUUUUUUUXoMbqvFTfh1M\/fvtM+aTWC\/MfKMxycOdv3tNeJXAKGz8ZsobreY6\/UOVy9+0yt\/u8w9jM7L6D49PaH4lnB\/Ljj\/APC44\/Vn\/wANf8hjdV4qb8Opn799pnzSawX5j5RmOThzt+9prxK4BQ2fjNlDdbzHX6hyuXv2mVv93mHsZlYX0D6V9PaH4lnBDfl7ozSd4HPt7wLgzrftMduf5MnSsH\/qHq5cNDVx0lVyBOxdR0hNOddYrezhGpwGcMzR5XvfpDjj6YhoCfDmSNCHv6C4n6\/6J3IcGOXvb+RjdV4qb8Opn799pnzSawX5j5RmOThzt+9prxK4BQ2fjNlDdbzHX6hyuXv2mVv93mHsZlYX0D6V9PaH4lnB\/wC+kpKep9B\/GF\/sMbqvFTfh1M\/fvtM+aTWC\/MfKMxycOdv3tNeJXAKGz8ZsobreY6\/UOVy9+0yt\/u8w9jMrC+gfSvp7Q\/Es4P8A3KKKKKL+FFFF\/sMbqvFTfh1M\/fvtM+aTWC\/MfKMxycOdv3tNeJXAKGz8ZsobreY6\/UOVy9+0yt\/u8w9jCmdGPQge\/p7Q\/Es4IZZEAq5jABqUrQe0uMim7WGcDRARq\/pAuBNFQmxhoEErU4QmAFUR4cJCLgNLnAwthJZlRxipaQAO0FAe+GAkGV5RfFxKJAdypOkcAjVBBUEilKasrEdtIaCoHg3QChzMNO7wVRVhAa0zEiVZRVDBQ32hQF+Ba7RxIocDYj4CEwDZh\/5OOOOOP+HHHH\/sMbqvFTfh1M\/fvtM+aTWC\/MfKMxycOdv3tNeJXAKGz8ZsobreY6\/UOVy9+0yt\/u8w9jCmdGPQge\/p7Q\/Es4IWCIoYABBqcHiaSqUAgEKhb5xEFbCOUaiWxYiIiCHk8wIQakizhJg1pCQqSKzVR0lDDcAAM3GBgdv9\/eBKqJhK0xAEy+MXZhHjy0AAHxKcdGFkzAAQpnSCZvAwBtPJIAXYVQhHC0NVISUABQbAcIjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjEYjADT3iOn\/YvOsURgHyOjrK6Y94QaxXpk\/kIPT5iqvfaI\/MIv5rFVb\/cRVsRVOiPUGI6T2h+IGgvlPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrPGs8azxrDQdh1n\/xAAsEAEAAgIBAgUFAQEBAQEBAQABABEhMUFRYRBxkfDxgaGxwdEg4TBAUHBg\/9oACAEBAAE\/EAoOSFAIKFDWN\/8A8HNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNmzZs2bNm\/\/AENmzZs2bNmzZs2b3hLMsAL8ryxWob\/\/AIc+AAAAAAAAAAAAAAAAAAAAAAAA99FLyh+kXh0CRwbllQZdJFpLRQGavIHTBfdyl0jh2+yGj3mXL\/8Aw7wAAAAAAAAAAAAAAAAAAAAAAAEY2VQ9r+EN4HxKHk\/JWNwNRcbUb5tXJ5uZsBXgPQgrvnFg2lyaaiCbJ8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkp8lPkotCFBMpCWOklRWorUVpiaBDbS0fNNxCnkPU\/Ab98gQP9BAgQZlSvAXhJJJWCVEj4B8MX+wDDBD4gYRMR8CP\/5hD\/zGVhLLDFVPcesBDCggvbmX4MwEiopLOSHu78T3d+p7u\/U93fqDBxsGHReG1Pd36gisG6Sq75yguWKsgouMGUMB2\/PICaUn1ue7v1Pd36nu79T39+oJP1YWsjQM92fqCdrMeQGt5ZjBLKlpCl0McYtlVGyGgtaLagyhSz4J7u\/U93fqe7v1Pd36nu79T3d+p7u\/U93fqe7v1Pd36nu79T3d+ocUNvsxPYn6idOQViqeh4kho9dAgQPCpXhcGEHMGDCYQYMHwuED4JKhAICURqKXLJZLI1CNRq\/GaqLEfAj\/AKqVKlSvGoHjXhUqVKlSpUr\/ABUqVKlSv\/I8D\/VQ3K8J4TAieH2H5+CYTvqICSByHSIKlW6gCsWFUp0zqJQKiLIyxXOHPZcMIEwwDWy076LnXGizAtByrnJE1bZ03gBtUKq6EtzM6jxHnCWg4C26ThstL4jbJw66zM52UAoIsu0VtKQwrOchWfqMRzB14hZ4WAcBbczJZCKTh6w2C8N22Sq09LQndfpMmC1yAWFKB04qFlMOUYilw1p4epoXqRgt\/KWGGMKDYkmGzYDpBWdi1BRfDtC9S9OWgGCgqniwl3AI0xI6g1UvJX\/rr5pgFcFqxTp1YDzu6i17w5D6vEkNPqoECV\/hmYXBhuAwgxE6Qnai9bHSHh8daCO4foljZwYUUn0gwphXUuHLjdFhrziWo1bZtrhgF2gDu+GTIlSaHUnoljKzeCk+jK+ZfzLTg3WjzWCGEq8iz1COkQ0i30H3lkg6vT5aMRcsYg+FdkwlfClkEVFoxXooqsf4P81KZaUynwqV\/iyY\/wA1KlSpUqVKlSpUqV4VK8Klf7pleAZgoDAlRloGUk+w\/P8AymFepQoCKsWycWY4gANmLlwVra\/8E2Z0FdbbQcBg4lBLry0NF2f\/AIxABpBaMDtT08BQYIEnDSh1HR4mhP3b8+ASpX+zD4CDwCtirP7cYd3XwkAtWC2dtM\/SFQKrLbWfVDcNPKLLzlx+h+SfefgnuXhMY5FNXS4khLKaqz1lI9IRRQvMebo8xBRwmElkrgPdcB3ZXCX33V9ZgCA8qp5hXrANGp2oh9S\/UggcLz7sSJLULW1dfKCl4k0eRGinDwuDbSXt1yibL+JWEkKcmBe5\/wCFQhKlSpUr\/wAKlf4qVK8KlSpUqVKlSpUqVKlSv83L8BmBCAjqPgYw15H5+CYn5gOtqOEvt6BEfNZDTofanoJ5rQkLeqfsXqfy1GHtzzZgp8xIWdE\/QqfVdEC\/lz3YeoCZcRZylfvcT8wHW1HCX29AiPmshp0PtT0E81oSFvVP2L1P5ajD255swU+YkLOifoVPquiBfy57sPUBMuIs5Sv3uJ+YDrajhL7egRHzWQ06H2p6Cea0JC3qn7F6n8tRh7c82YKfMSFnRP0Kn1XRAv5c92HqAmXEWcpX73OqQ3o3RX450D\/z+5Q0J9u8ZrSPVfmVDwrwqV\/ohFUdvki8eaRC7RYdbxHTfTMdq6w08o5POXHflfkn3\/4ovZ8J7X1eFOTaCYrQ2dmWEPpE+rcrHmW+32EYDAI6\/wAiVqM6fyciqYHwtXsKfglMtlzT5Q+syohtQX1kY4yD6oqHZaUf18oyVuXNpXledxK0raekqt3Fg1MYhUtiNS9ej9Zj0qdCh4IHmVANxe1tzXGvbzAMTV8CctlL1XpEALVoDrNeZVObpXP1mZcBULOsOYbQQxiFe7U3gVKaTh+sckbqDh78E0cnSsvmQmreafZwXBzraTa6RfAKWJeHki9aaxRM52EtMdEyXGaipJmJZXGB9auUaM0a+WBMueQIt5UxEsoWNq69yZE0n6N6kJug2d4KblAkB3oL2HWZBEC9hZs9IDdtZ1gtjtrcnpvVGdOIjfQQOMbp0vHJUAoc2KX9kPlou9HcnOJUSzkgsxizbTet7an0g\/IJWJa9cPnZnNO1jf4glI0gV+qWYjI8\/fX0qW7DSfuVKlSpXjUIJM4PAY1j4GEPtefBMuxpshFWAs6Q\/wACA0YuHRetrwERYbjBVZ3nAwDUaAyss\/FVKhgCpFgz6zQWpi9t9JM1kh9oiUu94TnL\/GgtTF7b6SZrJD7REpd7wnOXjloQgqtgDN3heOSBAUadNgPPVw5rxEwCOG0mFxZiUEurJa01R8LWVJTmk29bM8jAk5IVv6lNDXnpgPARKbehcWYipQW1eQ3HowT1DTcjrHzDRJZeTQujbnibasAqxpOR6xNl9IuIkgtODoHXxtSX6r8y5f8AhUfAFLeJXjtLXP34w\/B4KzDQCh3yfmWgCoNh5dO0rDTymR5+G4+35o6fv+KY\/wB9JwXB1aM3fEvrjcRbZ+6ybAVqp7c\/tX1iLAiUjsSe79JvN38PGZbtUvfhkeiI6gvV0iYmIuaPIiyrm8GbDhFb54ykGGbLOOljoQBeEhbsXdMfBjpueXcQAALEQR+kKA4ARrVQAxtT56\/U9r2R5MaNhU8hiOhRkYTWkHySaADvollDg8k3Em1ZRt\/Q\/IgYisFEPQ1KbCUFDo2sw\/GPoEatzTghetW4GcH0Rnlq31BPun5QMBPB1an7R4zLTcc+0F3rMTimD1eFu8u1zAdGw\/iEOUh9IvRzYPq\/m5SumXmv+yzseof9JRf7DB+JYnVJ7r\/kqTdj6v5SdKyravtaA7MVaRWUA\/UB6JwfOAS0J+pKn2L8RDLVoccAgsVijzYSwvcgg7wiyqi5V\/jFpGtpZcXtUEPsKeWf2lSpUrwCEB4BOkQhGHEYxtCBXk\/n4JhO+ogJIHIdIgqVbqAKxYVSnTOolAqIsjLFc4c9lwwgTDANbLTvoudcaLMC0HKuckTVtnTeAG1QqroS3MzqPEecJaDgLbq7JwFr9zS8S2QzroFpDqXzuUfw4Mhxd9M3rdTIvBoWVV0EnEQAzLjGNwSy3WMxg2izAGRaMqCc4OF119Rx+oSi6wF0pyLQUC6AV2SzkI8rTszF3VxveIgBmXGMbgllusZjBtFmAMi0ZUE5wcLrr6jj9QlF1gLrGJKGwHPO6ufKURRQfgh+2MuLlJqQMOHCUu6mbWtJcU2Q0FDEtbrbKhEwOobnOh08OShHNiqtmrbNKViAkrhp21UXVXOSJig0BMGR1PjEzVbjeOZ4lcsqXlDfaijVLFiyDV53C4z7g5q4LFDlAsIJdW0CMsH0OsKCMDrl76Bv1bGKCq0S0qh1Hy8Nnsy8DQhP3b8+NeB4ErwVK\/wGDTCQsjzCsNHr4SVxgb1fqVFKq2uVYMAbQPqQqOFBPJ8CONOen9QrHInoY\/YcJ7d1S4MERMicRn7F9RarmrVid3D+I4Gz9bD8ROBgvfX4eCrKYF9AUbigLoFGRHA6x+lgsWczR5QKrc+25XPVKsH2fSY0FkZp5UFwo6unX6Jc2kocDpGQBVRdzpdpRK1lJ35BN\/orsAZujrPcec972T7PDR5k++fjwqitFWid+PslGKZoD7hf3QVfS9mHkNfSCxVO7rg+ULsiR51KjbQkedxg7oV+k+6flPsIAZ7Z0lbBv97EuwlksXLKUBfnp+JSJd7zcRR2NB1wfmWdl9RC7je+zFM6H17fmELm0mm2+ryjgccs6lKEreXV5T9w\/EqfZPxDl5spRy0erFARSo07zaeMEDcFmSBVJwpUqVKlQIECDwEAmI1K1EiSoENeT+fgmXY02QirAWdIeBXqUKAirFsnFmOIADZi5cFa2oIDRi4dF62vARFhuMFVnecDANRoDKyz8VUqGAKkWDPrBVbBaUVlvJ0OSH2iJS73hOcv8Cq2C0orLeTockPtESl3vCc5eOcoURfccIdYsgQFGnTYDz1cOa8RMAjhtJhcWYlBLqyWtNUYADZi5cFa2omidUFArbmismOYEnJCt\/Upoa89PjWGTs\/OSGrsYDAYqwDxjzBNfN4bPZlBChAfvX5lSvEhDcuDLh\/ioaYPuJMuXLlEuMDo4HqQkwq7AHJL3cOkrq0fVoRaxEEM81X16y61tp6AV6rh9hwgb2aatUG\/DWh4nYMOaL\/eo+mPbnlWPdYp+phf1ju0sLXX8ma1GQ2pSlXUb\/0MPqRyglg4RfNZP0Isb6G4\/BHXlYLL8NPlPvHwvxg+07fiYZu9TH9Qi9F+hzOqQrVKrnkne8wq76X9IG0TwrdJ7DznuOyfb4aPObPN+IrONAHuB84eTsdRM7C4hSTlMnmEZqqyw9GzrGCw7HCt1HqtSj0GkeI+wNo8cod91SOCnFTOVCjzwX6E+6flCpm6gRudhPwR+mVerHaFfouCdLD7S\/dC8wbmkBB5MwXk\/SwfmZy7H1sP4IylCp7Bc3vBea3HbQo+Y5lex8NDWkZ4h5pJSnkG2kfdPxKn2T8Q5ebEZoL3i9PrEgpc9I0yx28DZmrpr6xYdFAVnnefKAuS0XBQ\/RKleFSoECBAh4WxUSbRYsuXFfk\/n4JhO+ogJIHIdIgqVbqAKxYVSnTOolAqIsjLFc4c9lwwgTDANbLTvoudcaLMC0HKuckTVtnTeAG1QqroS3MzqPEecJaDgLbq7JwFr9zS8S2QzroFpDqXzuUfw4Mhxd9M3rdTIvBoWVV0EnEQAzLjGNwSy3WMxg2izAGRaMqCc4OF119Rx+oSi6wF0pyLQUC6AV2SzkI8rTszF3VxveIgBmXGMbgllusZjBtFmAMi0ZUE5wcLrr6jj9QlF1gLrGJKGwHPO6ufKURRQfgh+2MuLlJqQMOHCUu6mbWtJcU2Q0FDEtbrbKhEwOobnOh08OShHNiqtmrbNKViAkrhp21UXVXOSJig0BMGR1PjEzVbjeOZ4lcsqXlC0sdosWw3kWTJZq7BH3rdRI0KNCeQVuygd\/Etd987lEcrnUAADtzqLaSAUqrhQw0cvPM2ezKGDCRj1H5lEx4VKleNy4KW8FsGWH9uMu7rK5+7GH4JX+AQoOkyHQRhTgGUBecYPV8MZGU6a2q6MD9WMidaZWY65aq7XV+JEusZ8OU1OLoU8yq\/epXhVFjD0Bx\/S4gvJSgeS\/ggW8VoPU\/BKbFrdVETRnf\/ACcE72oSq7n+ChUq2r18MxKFNX1PrLCAodDiXiNtERiHmoeuFT7wDeKlD1H8IYbS2racBuHYgycO9E7hs0YrqnbZPcW9HWGEekbrlK8HH3QFone\/48INySkxRY+S\/wA\/SPQqtNqy1F+6HMrXmBU9V\/CfZiR9mGV50GA6BOaUuVXZW8wybq8tUjqkaqkwiIW5V9IDQFrcMKLrP8MaLd7l12Il4bI1szXRcYtBWoosYB3olLWU2Kse9MBGJTxrzikpGGIAMhGs9R4gK3rIC\/WW6OswZvJaxb4KJ9XhoOAq4uVesqFK0qz5C82T4yLqSnoWfRZ9\/pF4KtNrK8K8KGBAgQICUlkWOZUSJlYk+1\/PwTLsabIRVgLOkPAr1KFARVi2TizHEABsxcuCtbUEBoxcOi9bXgIiw3GCqzvOBgGo0BlZZ+KqVDAFSLBn1gqtgtKKy3k6HJD7REpd7wnOX+BVbBaUVlvJ0OSH2iJS73hOcvHOUKIvuOEOsWQICjTpsB56uHNeImARw2kwuLMSgl1ZLWmqMABsxcuCtbUTROqCgVtzRWTHMCTkhW\/qU0NeenxsWAWqtnIZC6DsgMBirAPGPMEIKgyqZCwyYOznr4bPZlBFjFzAz2RZ9p9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fydp9X8nafV\/J2n1fyACr07E5OvhMJ31EBJA5DpEFSrdQBWLCqU6Z1EoFRFkZYrnDnsuGECYYBrZad9FzrjRZgWg5Vzkiats6bwA2qFVdCW5mdR4jzhLQcBbdXZOAtfuaXiWyGddAtIdS+dyj+HBkOLvpm9bqZF4NCyqugk4iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAulORaCgXQCuyWchHladmYu6uN7xEAMy4xjcEst1jMYNoswBkWjKgnODhddfUcfqEousBdYxJQ2A553Vz5SiKKD8EP2xlxcpNSBhw4Sl3Uza1pLimyGgoYlrdbZUImB1Dc50OnhyUI5sVVs1bZpSsQElcNO2qi6q5yRMUGgJgyOp8YmarcbxzPErllS8oWljtFi2G8iyZLNXYI+9bqJGhRoTyCt2UDv4lrvvncojlc6gAAdudeKLZ7MoPBbuSJQHOS5Rnbw9vD28PYwvSQvRQ9tD2UPZQ9lC+CHD20PZeBAkYk4nQ+BIk4iyJ0cKTrD2MPZw9vD2cPYw9jD2cL08PZw9jD2EPaQ9hD2kPaQ9pD2sPYw9jD2MPYw9jCdHD2\/gePbw9vD2cPZw9jD2MPYw9r4Gh2sHbwdvB28GVED8awPIcduxAcMQVsa4H2JetX4ltwR0h7Bh46GENCiepDc9+gTYlBICbzDikEhdRAUErU4EQNY\/x44EDtIO08BA7SDsoOyg7CLsoOwg7CDs\/AIO0i7KLsonoIO0g7eDt4Ozg7ODs4Oy8Bg7eDoGGuCnBsLeh0gxoDBqx5iCykprQs2Hicch9KFAEVYlk4sxxAAdbAcFVW1KxuoPKF\/l4HICuyrnsD8kSLCaA3sIptDODkDT0WWk5iPz86tcOGLA02acVfb\/g4460nMR+fnVrhwxYGmzTir7fE44GEFZ1oj1mPKcQBIEnA\/dPE45kCpNaRZFGBqFv4gAHWwHBVVtS4kFojnXFrhwxalStCMLFhWDxOOXW5iyOfANcHTLcGBXEqeg4l2pUvmyemRc2vhs9mUrHmwn2ndCoQ7EZBETtRghKiWeCpTAvcqMmw83ONd0XzdRjAXq2pQWQWlcQ\/6hKNhzsiJQpum4wANEUw82K7BbeDNfWMD2Qu+vapThJ1bUQsQ6lxmW1VN034WgAVy2PyiFUFBqzmWXSVlyQQtCdTMGaAroGNC0B1ZQh2DY9IuoB6LFtHDOIUWrOpKiTSB6LBroNZckxYOF3i588RqUFllu4GIDK6dnMoCAIZvGe84x5mJZKKbpvwULQHVxApIRADZn6TTltW1CkEyORgigybBMVPhExaV1dlXBgDLgBIpQI2DcTsm6UJcgJGhahvsOKjd0Kx3luTJEnAE3E0FN1Kgts1Gi5RwFPKE3jzriLZrbpKljI+kzAek3C1RW6RMRJrNZtCW2Q1sj3CN9ICRTOXEH6jZenWp3W1kXb2h9kHArU4d3g2YPaA19Cxu\/GpUqVKPBj\/AIqVK8DwUlJSUIK8j8mbpVwESzzAsYAiChWsvE+32hUUEpLr9Xkw+sb82512LfVACKRU8AWjB0jhQESoBglxtrEsjFqrSziVGW48NeaAqLRYoObcYKo9OIay9GV3uvCGBzhzIW2l3khEQWVBsF6uSGgABIhOR3H9QBdCwDSsyVhv1irFSBABv74OdwoNEQBbUq8kIiCyoNgvVyQ0AAJEJyO4\/qALoWAaVmSsN+sbAY32ZX9yFJQLJAboRy8pbhotZO286OmId2O4C7oLb6J26QaR8IKAHpJ059IwIrmV0PIGb+yXOWxTqpsY7RNkNZQKzVOLOE1XTZouK5dBGkXsssCLVLYJ6xMa4qwCOx3X9Rbq0CGHbS8kUAx5AUi1nmKkQmsTLJfC\/DZ7Mo3wakr7Tl\/gsL4pEhFv8CqjcuXnL039oGxkD9UCczgPIoiq8hY7W5ns\/TwkauVNFVj\/ALHZ7OJtH7L2iHSkAZ4EGAIa0rCf2W4hUcWNJKgz7OJ7DtDAijE6UUgEuSPrK0ho5MWtXj0iVmlvq7l6erudszWEQ4L5IAYlHLzs32hz6q06ciVDj7bYYMUYnSik+5\/NntPSSqFjTesGSE2VqrbykReL1aEWlsaqq2S\/EKjixpJUbfBl+uIQEKL+9IJ+WA8gCWj5HZlFVWPZWwtufa5m3vpCBjvL0pU4UKA1QRXwTnAvMxVhKNO6sqN6eggpPb9ka1ATnVMZgKUlUOsZiFu+UvOIYuC\/Ji0Zx6czqBAvqRsjsKgJHFfuNlXKCI0uMkFR5A+1xyWTruxPRkutLv6xo8w+l\/2Av04fQjK6y5OwTzg4sUoXFbv1g3eywd3z61CACgADsR57\/exUqVKlSpXjXgrwVKiSoEDwJ4MoK8n8mbaAxNNxLsabIiooLOTxOOrFArYrpd2S0aqByJX4QQHSxHRd1teBwisG6Cq684GEaisDKxZfK7ShgCpFgz6wDJTLvrAaZrJH6CJYveDzl\/g444DJTLvrAaZrJH6CJYveDzl4nHJjiImIpG+OfMgQVEYmwPPVw5rxOOEwEeQtDxZiUE+rFrTSjLRqoHIlfhB5giiA+1c8kpIyQFv8VNDXnp8Tjls0yOgAsMcl2agqItRUQQWJp6S4PARxbnttj4bPZl4mpK95yy5fgWLCEfAJLY\/0BUqFJQGXOIMBdUohiLiwLguzGO7CWGtOVGYrmqHLk\/iZAqBy2QvOYCONFSnpS7oXdRmypGWyNKhS7aK7SzEpQjkPTzmQ59TI\/wApdyUUxV07p7Qvlsl6sj9qg3Qu6hN1QctkNCijTwpE3wrAiXZuLZAi4eBFrhQ1nd1TgqBPVrTk9cpdhLDF3kr8QPYqgtXCQDGg07DncW1BmD7ECmhho0UaeFJnUKwJV2zFc0Q4eT+ILNQEFYSqfSZKEsgOBqj7R6h1DI4ai1Y2MeXOJc4UUxV07p7QrykvVkLO5BDLmXoKXy4B\/EZbCsF5QrHdjRSqUdP+oAFAuTHmPXtAHVoIWY\/QhZ6KW1XlGU6Ry3Z+4PiozWwfmUolxh2Fa8oFEIsyrqtKl6YEOR6D9ShCFBVhu4T2bCYespp6wgPYnrBKOVpACN1vtczGlxVOcwsu9gEgEVbWvmKK5lPD1dpegotibxHZUqtnVal0bHib6icQ0ioS00QOfKFRkiE1Tn8ib\/p+IJjRQGO0IkACj3\/s1Gogu2c3rmPoKBReGdEz8pFWMqv1jedM3TZ95UqV4VKleNSpUqVKiSoHisubz7X8nwmE76iAkgch0iCpVuoArFhVKdM6iUCoiyMsVzhz2XDCBMMA1stO+i51xoswLQcq5yRNW2dN4AbVCquhLczOo8R5wloOAtursnAWv3NLxLZDOugWkOpfO5R\/DgyHF30zet1Mi8GhZVXQScRADMuMY3BLLdYzGDaLMAZFoyoJzg4XXX1HH6hKLrAXSnItBQLoBXZLOQjytOzMXdXG94iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAusYkobAc87q58pRFFB+CH7Yy4uUmpAw4cJS7qZta0lxTZDQUMS1utsqETA6huc6HTw5KEc2Kq2ats0pWICSuGnbVRdVc5ImKDQEwZHU+MTNVuN45niVyypeULSx2ixbDeRZMlmrsEfet1EjQo0J5BW7KB38S133zuCOs5SuluwFK3WIqAvCoCsGxk48Nnsy8TUl+85YxmblyyFTHgQip8FJcUjUQiZhKZnwz\/AIuXGQWsJAAKBQS5f+ATMMAUfUlIl6WVevgqUxvolcGjiVKlSpUqVKioLRSQQFDASpUqVK8KlSpUMq1ALkpHgE6lLhQpMDypAzLgVWy12xlLV0mfOYaZVxoA9UwoiLmNzvAkT8je38zWMtty4YrMHoSvGpUqVKlSvCpX+KleFSpXhUqUQAz7T8nwmXY02QirAWdIeBXqUKAirFsnFmOIADZi5cFa2oIDRi4dF62vARFhuMFVnecDANRoDKyz8VUqGAKkWDPrBVbBaUVlvJ0OSH2iJS73hOcv8Cq2C0orLeTockPtESl3vCc5eOcoURfccIdYsgQFGnTYDz1cOa8RMAjhtJhcWYlBLqyWtNUYADZi5cFa2omidUFArbmismOYEnJCt\/Upoa89PjYsAtVbOQyF0HZKaq3DSwQsTT0hD0+VBylYOPBs9mXiak0Hq0QccRdP9wYMGDBAkg\/Kx+dj87H5WPzsfnY\/Mx+R8GT8vH5eCv8AeHzsfm4\/Ix+Rj8jH5GPysflY\/Ix+Vj8rH5WPysflY\/Kx+Rj8jH5mPzMfk4\/Ox+Zj8zH52PzsfnY\/Px+fj8vH5WPysfl4\/Px+dh8\/D5+Hz8Pl4fLw+Xh8vD5eHycLaHpmKbpF0xYqTLcq3aJQQ11icxXPxeFarEqFtlbN9BFvk+sfk4Y6+\/A\/6cCxXdD+FCwcMpJSukQZYzrH5KHyUPk4fJw+Th8lD5OHycPk4fJw+Th8nD5KHyUPkofJw+bh8lD5OHyUPkofNQ+ah81D5qHz0PnoXjVcWXT3Dr4TCd9RASQOQ6RBUq3UAViwqlOmdRKBURZGWK5w57LhhAmGAa2WnfRc640WYFoOVc5ImrbOm8ANqhVXQluZnUeI84S0HAW3V2TgLX7ml4lshnXQLSHUvnco\/hwZDi76ZvW6mReDQsqroJOIgBmXGMbgllusZjBtFmAMi0ZUE5wcLrr6jj9QlF1gLpTkWgoF0ArslnIR5WnZmLurje8RADMuMY3BLLdYzGDaLMAZFoyoJzg4XXX1HH6hKLrAXWMSUNgOed1c+Uoiig\/BD9sZcXKTUgYcOEpd1M2taS4pshoKGJa3W2VCJgdQ3OdDp4clCObFVbNW2aUrEBJXDTtqouquckTFBoCYMjqfGJmq3G8czxK5ZUvKFpY7RYthvIsmSzV2CPvW6iRoUaE8grdlA7+Ja7753BHWcpXS3YClbrEVAXhUBWDYyceGz2ZeJqTYrwAS10n\/APwXYMGDBo25Nu1anRo1a9etQIFi9evXo069evXr169evWrVq1avXrVq1atSLV61evWrVy1atWLVq9asdtMSdOdEJN5eUP3GA\/xT31omkgepfufxF6vBFHZeb4AI6xez\/PgG\/XtQgLWdv\/sQJFCBAgQIECBAgQIECBAgQLFgwYMGIAA0xTdhq06eBMuxpshFWAs6Q8CvUoUBFWLZOLMcQAGzFy4K1tQQGjFw6L1teAiLDcYKrO84GAajQGVln4qpUMAVIsGfWCq2C0orLeTockPtESl3vCc5f4FVsFpRWW8nQ5IfaIlLveE5y8c5Qoi+44Q6xZAgKNOmwHnq4c14iYBHDaTC4sxKCXVktaaowAGzFy4K1tRNE6oKBW3NFZMcwJOSFb+pTQ156fGxYBaq2chkLoOyU1VuGlghYmnpCHp8qDlKwceDZ7MvE1J9s6sIeJAlSoEqEEEEFv8AHKRIw+IYqVKlSv8ALVKlSpUqVKlSpUCVKlRJUqVAlSvCpUqVK6zHEuyqmAc3HtiGYEbFZlHS3qaiWeY6L2wjlXdC+vYxGdtwigXgNzIbOkCGxdUEqC7rphsBTyMIL6SpSHTLEqVKlSpX\/qvjcuor8j8nwmE76iAkgch0iCpVuoArFhVKdM6iUCoiyMsVzhz2XDCBMMA1stO+i51xoswLQcq5yRNW2dN4AbVCquhLczOo8R5wloOAtursnAWv3NLxLZDOugWkOpfO5R\/DgyHF30zet1Mi8GhZVXQScRADMuMY3BLLdYzGDaLMAZFoyoJzg4XXX1HH6hKLrAXSnItBQLoBXZLOQjytOzMXdXG94iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAusYkobAc87q58pRFFB+CH7Yy4uUmpAw4cJS7qZta0lxTZDQUMS1utsqETA6huc6HTw5KEc2Kq2ats0pWICSuGnbVRdVc5ImKDQEwZHU+MTNVuN45niVyypeULSx2ixbDeRZMlmrsEfet1EjQo0J5BW7KB38S133zuCOs5SuluwFK3WIqAvCoCsGxk48Nnsy8TUn2zqwh4VAgQM+Ag8CmAwGBlQJUD\/ACjLD4g\/0BUqVKleCv8AIH+ASVKlSpUqVKlSpUC8SkJYFahCZveEQCzDJ3ht4I1Xl9YofrwYWZhITDn\/ADE+46QvaXw2wIw31mcC1yy+kNNRTg7VBJUblDASJfgrwJKlSv8A4GfY\/k+Ey7GmyEVYCzpDwK9ShQEVYtk4sxxAAbMXLgrW1BAaMXDovW14CIsNxgqs7zgYBqNAZWWfiqlQwBUiwZ9YKrYLSist5OhyQ+0RKXe8Jzl\/gVWwWlFZbydDkh9oiUu94TnLxzlCiL7jhDrFkCAo06bAeerhzXiJgEcNpMLizEoJdWS1pqjAAbMXLgrW1E0TqgoFbc0VkxzAk5IVv6lNDXnp8bFgFqrZyGQug7IecsSSFj9w7QLKAHYW+GvDZ7MvE1J9o6sIQYQIEGZUCEB1hAMp41JSdktBy0a7jCxcOhO1L9JedrwqSMrwxCpTLS0tKlPgCVKZUUy0tKlSvCyU1cMtRKlquaaIHYxuOwbdQgxHULYlsrMWtYRRxklXpZ0pcEaiuBA1Na9B0lEFrrBY8dpnATIkS7DzUOLVRcFByvcIuKaTIYJYyqlkqMslRlypfgF6l5UplMtKZ2lpTLRUFeR+T4TCd9RASQOQ6RBUq3UAViwqlOmdRKBURZGWK5w57LhhAmGAa2WnfRc640WYFoOVc5ImrbOm8ANqhVXQluZnUeI84S0HAW3V2TgLX7ml4lshnXQLSHUvnco\/hwZDi76ZvW6mReDQsqroJOIgBmXGMbgllusZjBtFmAMi0ZUE5wcLrr6jj9QlF1gLpTkWgoF0ArslnIR5WnZmLurje8RADMuMY3BLLdYzGDaLMAZFoyoJzg4XXX1HH6hKLrAXWMSUNgOed1c+Uoiig\/BD9sZcXKTUgYcOEpd1M2taS4pshoKGJa3W2VCJgdQ3OdDp4clCObFVbNW2aUrEBJXDTtqouquckTFBoCYMjqfGJmq3G8czxK5ZUvKFpY7RYthvIsmSzV2CPvW6iRoUaE8grdlA7+Ja7753EJsKksKy9mIj41QRyhh7eGz2ZeJqSPacsqBAgwYMGEEEDcEEqBKSngqoMGELbKJT\/cDAiSiUSiUTMzMwFf8AEqVKlRxFj4VEleC6pLXZKeYKUguux1cxXBUvwF3GaWjU0MYatLXKgUEFEuFSNNx0uO6IqKcE1gUrxhBba4iXqUVKZjcbFw8RoY7BzKZiESkyyx0j0lEKeYkohACB2lNaiPSA9IEWWYKHhsrJTCCWPOIfnwmXY02QirAWdIeBXqUKAirFsnFmOIADZi5cFa2oIDRi4dF62vARFhuMFVnecDANRoDKyz8VUqGAKkWDPrBVbBaUVlvJ0OSH2iJS73hOcv8AAqtgtKKy3k6HJD7REpd7wnOXjnKFEX3HCHWLIEBRp02A89XDmvETAI4bSYXFmJQS6slrTVGAA2YuXBWtqJonVBQK25orJjmBJyQrf1KaGvPT42LALVWzkMhdB2Q85YkkLH7h2gWUAOwt8NeGz2ZeJqSfacsqVKlQIEDwITFiilwzMzMzK8FeFMpmZcJUrxq43j\/iDKU8FYUlRjLly5cy\/wAkkDxUwgvEZaKY4J9IDUY1LbzDfZOxA8K21NZ0jxZCICghO4PLAQxUoZQdDEE7pvLBiaR5iH1EEXVABToRsmKiYAyxJVQbxEVcUYPAOqmDcepOoi4lGShqEUiIiUlHgT7A\/PhMJ31EBJA5DpEFSrdQBWLCqU6Z1EoFRFkZYrnDnsuGECYYBrZad9FzrjRZgWg5Vzkiats6bwA2qFVdCW5mdR4jzhLQcBbdXZOAtfuaXiWyGddAtIdS+dyj+HBkOLvpm9bqZF4NCyqugk4iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAulORaCgXQCuyWchHladmYu6uN7xEAMy4xjcEst1jMYNoswBkWjKgnODhddfUcfqEousBdYxJQ2A553Vz5SiKKD8EP2xlxcpNSBhw4Sl3Uza1pLimyGgoYlrdbZUImB1Dc50OnhyUI5sVVs1bZpSsQElcNO2qi6q5yRMUGgJgyOp8YmarcbxzPErllS8oWljtFi2G8iyZLNXYI+9bqJGhRoTyCt2UDv4lrvvncQmwqSwrL2YiPjVBHKGHt4bPZl4mpKe85ZUCVKhB4ggIEMQgbm2ZiUeCqjUxKgxEIkSEplpTLQxLJZ\/u5uIxGMWXLlwz4leBLhAhKd8QuwbgZKpnWY1FbERcEvlhDEHrHkl8EgFrcSsaRxZkyjpBWc4iZxuX7iCGiItlmg9UENPWNWnrH9holTnUWRcHEE2PNxTChVRuKUxr1BGjE6mWswmW8S0VgpLdYShJmnm8GCzEViyWRSp9g\/PhMuxpshFWAs6Q8CvUoUBFWLZOLMcQAGzFy4K1tQQGjFw6L1teAiLDcYKrO84GAajQGVln4qpUMAVIsGfWCq2C0orLeTockPtESl3vCc5f4FVsFpRWW8nQ5IfaIlLveE5y8c5Qoi+44Q6xZAgKNOmwHnq4c14iYBHDaTC4sxKCXVktaaowAGzFy4K1tRNE6oKBW3NFZMcwJOSFb+pTQ156fGxYBaq2chkLoOyHnLEkhY\/cO0CygB2Fvhrw2ezLxNSa9hy\/wCCDwKgQIJUCi6vtKAs5IcecQAHHqQQeAwxUCI9V8V1fWo4t2Wm\/qkjt2KnB\/Yxsbpxj7w0h4DFQQCsuw\/sN3V3P834L8CJEj\/kHDxNgNqoP7UTfSZEHeOgMjKZmq8IWsdXEx1+L\/IzVoy6jQA4AuswjIuVF\/WA2AH6voRpYDbr8waQbpAIi1xsaeopH6MAFERRGNZTfcygMi8vZWPnFa4VBYISYnGJy36S3UdcnOUbSwt3eYgEV7THGozyzm2J3clDiPKGuY4PwLnkY4xr6zAUJi6iYidCOoMR8EUb8BCMWjuDUtYqEd+S\/PhMJ31EBJA5DpEFSrdQBWLCqU6Z1EoFRFkZYrnDnsuGECYYBrZad9FzrjRZgWg5Vzkiats6bwA2qFVdCW5mdR4jzhLQcBbdXZOAtfuaXiWyGddAtIdS+dyj+HBkOLvpm9bqZF4NCyqugk4iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAulORaCgXQCuyWchHladmYu6uN7xEAMy4xjcEst1jMYNoswBkWjKgnODhddfUcfqEousBdYxJQ2A553Vz5SiKKD8EP2xlxcpNSBhw4Sl3Uza1pLimyGgoYlrdbZUImB1Dc50OnhyUI5sVVs1bZpSsQElcNO2qi6q5yRMUGgJgyOp8YmarcbxzPErllS8oWljtFi2G8iyZLNXYI+9bqJGhRoTyCt2UDv4lrvvncQmwqSwrL2YiPjVBHKGHt4bPZl4mpN1vbaEHiCDxJJOMCNgCMSkDW4Qiidjl18+I9ua0mze8EJgatGuCA\/NtNld4z7hgxho1TL0usGOqr3qVdadQsOuBNQ27c5pusMQFCJi1l9aroiDcARRs+tw2BtH5YTxghSlX0vExAbKH3gV4LkXs5uVQs4Jf0oIwglBY1k+8rzlKIqghuqD9Spd5j5WKWqGIvqIsR2SWs0\/mKpMPLb9ZUVihbveYyAJ5h9EEOmD1OX6QghqrBS9gLh8ojLtPPUEc4NGXm41Twcr+8FbAuisfaJOALE3rP5gwu1FwVSXWogNmO7PS5TlcgGU54wcfm4Swp1rnjiO4crskQWqaNDLM1gu19JbT1BRedQ6AXkrpKPMLhwSutdv1Ja\/gGeq3flHecFa7puanWn5jxzygs+tkXNKciirDGe8e8rOlhRXJzEOCb7RFnEREEqk4f8JRgrwrpgrH2R81jtwN8zEWDsLpgyItQ0Yp2i8wYMTAHAbWWUNAAaorqwldM5xnllcEBuS7uLEdqBlxr6QoFXCw+kDkgVSpl6RJWVudZjAgXbTQ2XvMvmpSJg23gljKqQbMnECkwNXKGsORmABLACsN\/mGllAuqpjYBZruz0uIhDkAkpZ4H2iFbzMcJYFGDbERiK5ZVEwyrLI1RohwoRbxPsT8+Ey7GmyEVYCzpDwK9ShQEVYtk4sxxAAbMXLgrW1BAaMXDovW14CIsNxgqs7zgYBqNAZWWfiqlQwBUiwZ9YKrYLSist5OhyQ+0RKXe8Jzl\/gVWwWlFZbydDkh9oiUu94TnLxzlCiL7jhDrFkCAo06bAeerhzXiJgEcNpMLizEoJdWS1pqjAAbMXLgrW1E0TqgoFbc0VkxzAk5IVv6lNDXnp8bFgFqrZyGQug7Jh03Q\/SwMhw74zAau86GjKVjePBs9mXiak2t9tpT\/K0wuBAzCJ4BwukAbMf9lzlTkT\/st3vsWh9o1tAg7q+bYsCETa6e0YSFulj5rklbiy0CM\/UgUyF5Qo\/LKicRRlv2qJvDQXfd1EK7Xy6vWAKpqu453EZWAW6tOcahdQV7X+ohJs3VvwuO0ho\/piVK7DFAQqDEKDOEYQYZyT1qCR0KIYEaGniFBHqoqx6GBpi38x1bTXS5UagEe2NlyxKg2bea4\/Fopa1GdXRRX7YFEs2oK84GgM13HKdo6caovHnDSSLLM152wKCFVLv0gYwCjFP5iIWjCCpTeNxSS+0PmOa0ptLViOC7eWdEBornziAGpsa+kAaAFivqxRkNhmL8bmHdA9JVsW2qTXkMIDQ5oKpj1lCsYtssrAbeFspzsVv6wbQTu\/bMwJVZxzfHSYMBRtaHYVuPw8xVRjoS2GHBSz0sm1mETsI\/dFgXbIV9AhqKaQPSmDKhZwBbxEIJkVgvC2KhmqAuD631RtGAKUGKtU9IqNdHAK6DFQGNwc+X0iwLT3fyg0Ciop+4AEOWdbhJQYUaT7MyQQwwPUTFh24nPOlHHS5e1SOSAhPvuV2NCsMt9jEFAqQUY\/7MBoMIQgZ2uD708C3vsYABRQy6lKxHFxFpNsxBogDZKaRRxMYHWDLWUqG47cutQnKyryT8+EwnfUQEkDkOkQVKt1AFYsKpTpnUSgVEWRliucOey4YQJhgGtlp30XOuNFmBaDlXOSJq2zpvADaoVV0JbmZ1HiPOEtBwFt1dk4C1+5peJbIZ10C0h1L53KP4cGQ4u+mb1upkXg0LKq6CTiIAZlxjG4JZbrGYwbRZgDItGVBOcHC66+o4\/UJRdYC6U5FoKBdAK7JZyEeVp2Zi7q43vEQAzLjGNwSy3WMxg2izAGRaMqCc4OF119Rx+oSi6wF1jElDYDnndXPlKIooPwQ\/bGXFyk1IGHDhKXdTNrWkuKbIaChiWt1tlQiYHUNznQ6eHJQjmxVWzVtmlKxASVw07aqLqrnJExQaAmDI6nxiZqtxvHM8SuWVLyhaWO0WLYbyLJks1dgj71uokaFGhPIK3ZQO\/iWu++dzh\/mGQyo8oPbMzRmjLoawAKvu34bPZl4mpO6IQIGZRKSkrCA8UEBAS1TDoUICAIBEiWJfqL9IkSJ2dpggfZuC74Ji72S12lNYLwXzCm4DaB2bmPCokqVKlRlQi0oLQOrgl4sCLA2lLCiAnQtXLMFcdAzqhIIxDUtEE5guE6gag6rUuIIbSjlgsj5mYAtR49ka3kV1lqbYcbtBoRDLLKa0aFphFtnznEwcy\/0uUzLA4zllBrEvop9YVUuwuOmHmKiiNF4Nx6ArgVn8QIUFj1Wt5l0PHvpBGzEKazlIlcodwLZUFRtaEbBGohpiDIxxjLuXVMFQS2CWh1Huo8xNLwG2IIlhgZhRLNkD6w06H58Jl2NNkIqwFnSHgV6lCgIqxbJxZjiAA2YuXBWtqCA0YuHRetrwERYbjBVZ3nAwDUaAyss\/FVKhgCpFgz6wVWwWlFZbydDkh9oiUu94TnL\/AqtgtKKy3k6HJD7REpd7wnOXjnKFEX3HCHWLIEBRp02A89XDmvETAI4bSYXFmJQS6slrTVGAA2YuXBWtqJonVBQK25orJjmBJyQrf1KaGvPT42LALVWzkMhdB2TDpuh+lgZDh3xmA1d50NGUrG8eDZ7MvE1JoXtyhAwgMCBCGAgTXwkk4b0H4Y8C2tXXlwyocVgKqmcoIKsKXdBa0G2HEEks24YfslFhCzm9tCriPErgKb8o00nTmNu4Lun0JjmGXAazNZX6RNmVEckoFVbHGPvLRUDaRXFbLSr13iUjLXqcuMeBWGJN4faHLVIrsW94BSlqtwfTlEBE9mg+suZDTVNmuOJRdQNrfuqiW7dF5PQsJY0c3an\/lS4jysC+gxMvNCnqjBVyoTnojKSiiy9jMTCyBZzy1AE7OUYPLyjISzeWsuDhmMkKjZWJRxfaCMZK2qlVnWYIrgu+NdIjKxsXUotStBSYw3\/IFK4WvhE8ibbojCbYWs7xHoOVLxV9amdKjoxXnRGNiHaFiceUPAVajPMRq0jiseszIi0BaZ7ylLJtX8QBpqG6bqfnw1+oPPGCTA\/WPwKjSKrC3Z2p2g3M05HlxZvrK4BVq8xGxhLxTn1lRbpHqN05V5ZRFCXey7q66feZZE1Y2fqLhaloZ2yH64\/cFyauFAdgrzgVLPgLxe9kRgZR1m0YOpR5sWMB5u8w1BgDKT6f0ssbSj3usxGOAOQdv6mC6nGw2YhVqWVMpRHEaN3K3gDC2kC14BeNMuahDEucwUMJE5\/Y8JhO+ogJIHIdIgqVbqAKxYVSnTOolAqIsjLFc4c9lwwgTDANbLTvoudcaLMC0HKuckTVtnTeAG1QqroS3MzqPEecJaDgLbq7JwFr9zS8S2QzroFpDqXzuUfw4Mhxd9M3rdTIvBoWVV0EnEQAzLjGNwSy3WMxg2izAGRaMqCc4OF119Rx+oSi6wF0pyLQUC6AV2SzkI8rTszF3VxveIgBmXGMbgllusZjBtFmAMi0ZUE5wcLrr6jj9QlF1gLrGJKGwHPO6ufKURRQfgh+2MuLlJqQMOHCUu6mbWtJcU2Q0FDEtbrbKhEwOobnOh08OShHNiqtmrbNKViAkrhp21UXVXOSJig0BMGR1PjEzVbjeOZ4lcsqXlC0sdosWw3kWTJZq7BH3rdRI0KNCeQVuygd\/Etd987nD\/ADDIZUeUHtmZozRl0NYAFX3b8Nnsy8TUkFvblAIBAliJYNFsQwaJ1oPEMC4YmaZVwynhlXELCy+t+fBK3dnVt5Y9YuyfCDEUBLuEz0jiEKrIN82ETwBIQO6KpniOjYdWPKWDAbMPlKMI9qfepa0taII0+YXuNvJW+vKE1DOS9GitS1ExDpd3hlvaC785YcQrGZiKttQlLCmMyptK10qGFqwVKoISumqM+DnrBcl2UtPSUVIGgp1oNxaTNYpVbqOsKqgLbg6FsarKsUqtwp1hq1BacsfVIgFMAbfooxLEUv0BUsJdQAaGucn7lIC6GbM3MSGGmx3mpUIBpKLzb+w5ih1b7wthfaAlbKuTmKrAN2p\/SBXulduPKKwY85zV\/TcQYUnkwFxlrZpbiP0qxav+RAAKLyzY\/qXihnCr6KTfDPFxwXZfrmCbYVjIYSOsB00rsGfSHSgRKENaOENREBnIsiUUu4mvdFf0mZV2WwOKxcoySyBAMLqvKJc3tspPTlm+kvIH63MpxC7sx31ntKt0tQSmcsHpmXQLbdtM9FibcS8n15ihVCynRgim7F9QuoicJkdogaUypAQgutHP3hqEmbu+yhTEEKcn5QeS\/bMKBceuU+xKI2Uq+b0gKKxSwpp7QtlqKqCnJqILpymfuIXKLTIN0+MBVmI4WUYk1BwUjzQXiDNx4WUb8IEXLuGieWPyfCZdjTZCKsBZ0h4FepQoCKsWycWY4gANmLlwVraggNGLh0Xra8BEWG4wVWd5wMA1GgMrLPxVSoYAqRYM+sFVsFpRWW8nQ5IfaIlLveE5y\/wKrYLSist5OhyQ+0RKXe8Jzl45yhRF9xwh1iyBAUadNgPPVw5rxEwCOG0mFxZiUEurJa01RgANmLlwVraiaJ1QUCtuaKyY5gSckK39Smhrz0+NiwC1Vs5DIXQdkw6bofpYGQ4d8ZgNXedDRlKxvHg2ezLxNSdvvyhCGyLApLCmLK3GC9Igx1Ru8JiyeP8AwqzIowfpE2mFOIlmI7gLssMMnrKqnULxlCLnyhJIpWfdESiNjMKdYknUiqBSKEUbER6EIaw7dvqwuS9AqJXMW5gSO0e7pArzGDDwnKgzUOSy9ojiwsBSJcpTQCj9w7cjtAF9WqIhKdv3Kj9IAMDHE3puatk6zBXKLaTC8pYx3xK+Uq94BQmYAo3BSlYjNVNolSoVbSKmVL4MDFSoTbGsudagBF5xSpL3JnsA9IAZghzGa1FxGqLcFBJvIEqCIJLVkDizEE5eGOYGSAueXQ\/PhMJ31EBJA5DpEFSrdQBWLCqU6Z1EoFRFkZYrnDnsuGECYYBrZad9FzrjRZgWg5Vzkiats6bwA2qFVdCW5mdR4jzhLQcBbdXZOAtfuaXiWyGddAtIdS+dyj+HBkOLvpm9bqZF4NCyqugk4iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAulORaCgXQCuyWchHladmYu6uN7xEAMy4xjcEst1jMYNoswBkWjKgnODhddfUcfqEousBdYxJQ2A553Vz5SiKKD8EP2xlxcpNSBhw4Sl3Uza1pLimyGgoYlrdbZUImB1Dc50OnhyUI5sVVs1bZpSsQElcNO2qi6q5yRMUGgJgyOp8YmarcbxzPErllS8oWljtFi2G8iyZLNXYI+9bqJGhRoTyCt2UDv4lrvvnc4f5hkMqPKD2zM0Zoy6GsACr7t+Gz2ZeJqTt9+UIQhSW1KsBdwjJs3KFYGg3O3LdJTpKf4424kQ8JYYU2ykdQeRCLAwqZnEtdxU0wFGampR9Zo0+bDtBXLKYMY7dWtEb7jAXW8Fwc61ES3cvJ4FhdfapQGBeB2PLEtAeQv8RKpgbw4hnBQLcyrVaH16xwVujfBnTFl4U0REvqS3IuoaxwlWecLcpvYDG48gut5PzG7ZX9vWOVrIWmLheA43ePzGqVco24G5j4gtUy4+8CNMNaq6Iv4InbkbM195dJhBSonAP5DhqXzY\/c2BVdxz3CVsWn0\/MzpRdG6wnEWctHBKwQHSqv1lDRVqadm+IhiRVZmqzu6hOqotvnEW4osOqh4tJ3r8zGhHmcGo1qMsKLIAOqKnSXgd3uahqDm1F7BcGpQY\/eOgjOGS3oOIizFiZ8ojmCwwqKzwMW5nJYhkYLlKeY5qN3MGNwfLcu0zYZfQjcvT8nwmXY02QirAWdIeBXqUKAirFsnFmOIADZi5cFa2oIDRi4dF62vARFhuMFVnecDANRoDKyz8VUqGAKkWDPrBVbBaUVlvJ0OSH2iJS73hOcv8Cq2C0orLeTockPtESl3vCc5eOcoURfccIdYsgQFGnTYDz1cOa8RMAjhtJhcWYlBLqyWtNUYADZi5cFa2omidUFArbmismOYEnJCt\/Upoa89PjYsAtVbOQyF0HZOd1T6KuDqxbaIrdEEZRUrBx4Nnsy8TUlZFfKhUTC3G0GRNKbBJkzcUlS2TSrkAMTNjC5d0Rp3iFaBrZKOiB4MzSUEs4gV1OlhbcNGIM+Fe03gXuCoNzPESEsamgW4mc5\/EvyHRhPs+kwcGN3TxC6kRtWqJReqzfLBuOmzvcDvmiM1fWOxV070RWosD90iZSxFr6xs6sirqqfaOKExqogvp8uYTCUV5EBdtwHRipCNqXW6\/EKqlxRglCUA08ZbPxBoMCz1VCMq0FH2R06CqS6aoqvvHqNXjqlwJGUjqKKFlr+hBXxNvmyxmKlX+8xgWui\/pklEHK761iK+xV63LgMir3PMdME7Lr9EYaSkNMMYtEy0Zhd2oVKkBpy1m8mEwWhS9\/WoihAvUZ0ktqxOdBLFjcQUcVEKo+syg3GGNTODTDEEVlhlJmKxCklvWEhVqZLh0hJS95YqEYrYYBekTiK6ywljEMXlgK2qqJOJSzGFYcUqDUSneiumNY4\/I+EwnfUQEkDkOkQVKt1AFYsKpTpnUSgVEWRliucOey4YQJhgGtlp30XOuNFmBaDlXOSJq2zpvADaoVV0JbmZ1HiPOEtBwFt1dk4C1+5peJbIZ10C0h1L53KP4cGQ4u+mb1upkXg0LKq6CTiIAZlxjG4JZbrGYwbRZgDItGVBOcHC66+o4\/UJRdYC6U5FoKBdAK7JZyEeVp2Zi7q43vEQAzLjGNwSy3WMxg2izAGRaMqCc4OF119Rx+oSi6wF1jElDYDnndXPlKIooPwQ\/bGXFyk1IGHDhKXdTNrWkuKbIaChiWt1tlQiYHUNznQ6eHJQjmxVWzVtmlKxASVw07aqLqrnJExQaAmDI6nxiZqtxvHM8SuWVLyhaWO0WLYbyLJks1dgj71uokaFGhPIK3ZQO\/iWu++dwTqRCkaiDzPQY14cQHBOwWN\/R4bPZl4mpL2GY1OCGnlDvLxCzXU6cu42igbm2JbCmUpSS95jY1DGYPgJbiFt+DBwRUCUqLPhHKDMIIGYGInSA8yiNmohwwY9IoKptLWAhzQIIUt3llwXUMOYqLEYGgfUI3Y9QaY05B3VmQQmhuKiKfrLdoAZw7BSVCLartW61NSW+gTzjAdhxzUHtUPnTFrbrEGpvtUqeRCMivq3Ep0nEIuptAG2T7w6ANIBlgY5DkeOEfvKu4IqK9WJgANCXKOADQSq3UoIRNwWY7XEAsSnrIRUKteqHHuIKKlPXAssE0QQMzSwgGMIWAUQGQqPsDqTl2LAdwy0RWqlpVyrDFCj08y7E3jUtIsW5cuZs2ItpBsxX5X5+CZdjTZCKsBZ0h4FepQoCKsWycWY4gANmLlwVraggNGLh0Xra8BEWG4wVWd5wMA1GgMrLPxVSoYAqRYM+sFVsFpRWW8nQ5IfaIlLveE5y\/wKrYLSist5OhyQ+0RKXe8Jzl45yhRF9xwh1iyBAUadNgPPVw5rxEwCOG0mFxZiUEurJa01RgANmLlwVraiaJ1QUCtuaKyY5gSckK39Smhrz0+NiwC1Vs5DIXQdk53VPoq4OrFtoit0QRlFSsHHg2ezLxNSaHLKPPMfzxsK7iGtykt1gSKmt+DfPwtli4DRl2peEMDGjC0ViwhdTMeIm\/AEBmYmRCL9JlwY+\/ugg1tjVa42xYb4nmhZGoW0XQcxEZommboWA+wFhY4fK4KZJfmCFF5Cp+4xaTcZuUWvLmXkLaUmSWusMPEAtadN7uo2HMrS78rhhYx5ZHnBAVsTIjSycm+kwFJzeJcbjbA7IGaPoQYsGg8\/WKwoUtG2WY0VTG75YMMFW3n+QbjbYFI9IudJi4RB0lSoAtAdXG4ZSmoKEKwgbVLvpi\/zAt47ywqp0lQS1YhFFZPWKZX2lVCooQ1iouW4lsjcyxVcQmyrj+hHQCBFkBijNzmaajqEKsviYRzcrNwXMCpRCjDGJuFyo2XccKOp7C78EwnfUQEkDkOkQVKt1AFYsKpTpnUSgVEWRliucOey4YQJhgGtlp30XOuNFmBaDlXOSJq2zpvADaoVV0JbmZ1HiPOEtBwFt1dk4C1+5peJbIZ10C0h1L53KP4cGQ4u+mb1upkXg0LKq6CTiIAZlxjG4JZbrGYwbRZgDItGVBOcHC66+o4\/UJRdYC6U5FoKBdAK7JZyEeVp2Zi7q43vEQAzLjGNwSy3WMxg2izAGRaMqCc4OF119Rx+oSi6wF1jElDYDnndXPlKIooPwQ\/bGXFyk1IGHDhKXdTNrWkuKbIaChiWt1tlQiYHUNznQ6eHJQjmxVWzVtmlKxASVw07aqLqrnJExQaAmDI6nxiZqtxvHM8SuWVLyhaWO0WLYbyLJks1dgj71uokaFGhPIK3ZQO\/iWu++dwTqRCkaiDzPQY14cQHBOwWN\/R4bPZl4mpP3KX4DGGSGLTEJU1FDDq1LvUGoV1DklDwLywgTKHhX1glTMxYlIxeJYDxFeh1BegsZ9lYMSxoCuWKapgkGJWfpVFlqCAUARY35QCsL5dsU0pv8y9yJumXqhzb1VtowMm76yif7hOckFxBdNl9SGkNt2DWuqJAFADdWzX0nI0G+jFJscPyBzGbR4FTdBYUwYqXDbT3WpCyHRDN5VojuBbNGiO7up+GGmHIt\/OIwcpR6DP3uA2SovoP\/CBymEa7xdGwPu+kutGOT6xcQyqU046d5QKAXxF6QoJwQh9P7C6oA+rCLYCtt8EKSQZG71xHKmw2fmNJi02Qt4AaqCue0W1gvWnKJkXUWQE0gGuptKXTYihjNAUwIMq5yMQvBaGhYdYurQRHBuPnVYlLe45Oky4u4GgDEteqQ\/NYviJa4ZhRjm5SzuL8kCMdbwQuF2ZoDaE2wEysLS2XhiCr+0KqKSrBmBk6g7ERMolKIh0Py8My7GmyEVYCzpDwK9ShQEVYtk4sxxAAbMXLgrW1BAaMXDovW14CIsNxgqs7zgYBqNAZWWfiqlQwBUiwZ9YKrYLSist5OhyQ+0RKXe8Jzl\/gVWwWlFZbydDkh9oiUu94TnLxzlCiL7jhDrFkCAo06bAeerhzXiJgEcNpMLizEoJdWS1pqjAAbMXLgrW1E0TqgoFbc0VkxzAk5IVv6lNDXnp8bFgFqrZyGQug7JzuqfRVwdWLbRFbogjKKlYOPBs9mX+TUlr8GMINZiRvKJhy1AmGOq404YXSqRO2NmVKuFYLdyruHViHEXEWY6gpA5TLf8XF6LO0+qL+sN5YCYBzm4EFCIjTf3ZfAnoK\/JlHYBAVsajO1dJgW8QscwU2OWoUH6IB5BKDYZMoGO7MahCwUTvTC8snSj6aiK6uxryIUU2Vum4adGpTQz3coooEFaacSZ+lBpBfn54jy+kTCcsr7c1EXdymJZlF\/WLIWUW+UAyzKdnEQNno6rpEla1V0XdbRfYm1jI6IFwFfe4WrAGQKqq4pi4BWVq37xEbOwZNVoqX8h5ol+qwviSrtf5mDW9XrLtpUCuCYu2nqvRZl4MW6Kq\/oY\/Ksoph8iiIm5XTh\/fvBAcso\/CPqMYhp2TYXEctjA+BjVFmV4TM8jiZ0OoyI5aMvhrzAuUCNVxG8Sai+PAlzKDG+ZSLazUIPRENQjFYso3DKAcxxaZIRrXP5PDMJ31EBJA5DpEFSrdQBWLCqU6Z1EoFRFkZYrnDnsuGECYYBrZad9FzrjRZgWg5Vzkiats6bwA2qFVdCW5mdR4jzhLQcBbdXZOAtfuaXiWyGddAtIdS+dyj+HBkOLvpm9bqZF4NCyqugk4iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAulORaCgXQCuyWchHladmYu6uN7xEAMy4xjcEst1jMYNoswBkWjKgnODhddfUcfqEousBdYxJQ2A553Vz5SiKKD8EP2xlxcpNSBhw4Sl3Uza1pLimyGgoYlrdbZUImB1Dc50OnhyUI5sVVs1bZpSsQElcNO2qi6q5yRMUGgJgyOp8YmarcbxzPErllS8oWljtFi2G8iyZLNXYI+9bqJGhRoTyCt2UDv4lrvvncE6kQpGog8z0GNeHEBwTsFjf0eGz2Zf7NSebisIPBFmW1afYiVAt9Oz1MSwggjZ1FEUqAEsvOE11YlvOQxDceiRHBUw1BdWGPrMEb4GyL6RJpGXMMSM3ghmKX4VENtWGvMREu6sr34qbiMEAotq3BGQgLcj8AiuEGGKlvABx2pf0iK4hjccxOHvGyzrTA84ZZBdjgOwUDzGEGYrcozNcxAFhiYekcrDw0dIn9lNwNlt93KU6AdgxBKPAq8+VhEV4SkjVKA1uWvLFr9IVILKxePQQixY8xrwp2j1qJgz2alDV1DGfrB\/YocAUSVfTDHzuoaCIS6qBpfSh90scvbXinvHToby4Ip7NBArfQyhHPeMSwORK0YTnYFm7HkZPAjsBw3BvWWLyUcrzWMAWOg3mOPMIgoZuactQQVY5E\/7FjIGXkz1qPSoNl89YUC+GizPczrriJtrS7OkBDafTooq2JAv1vf1Rsu3HEClOGAWLm+haFQ6l52g23LAyygWHpEYYh0dlnr4Jl2NNkIqwFnSHgV6lCgIqxbJxZjiAA2YuXBWtqCA0YuHRetrwERYbjBVZ3nAwDUaAyss\/FVKhgCpFgz6wVWwWlFZbydDkh9oiUu94TnL\/AAKrYLSist5OhyQ+0RKXe8Jzl45yhRF9xwh1iyBAUadNgPPVw5rxEwCOG0mFxZiUEurJa01RgANmLlwVraiaJ1QUCtuaKyY5gSckK39Smhrz0+NiwC1Vs5DIXQdkN6+ldgThBnkIM0CRSqNro1jw2ezLxNSUxWy2W9fAO8aOY1IIiwIHlNQgaNCFS8ODf0l5UgcOGbDTEvU0BOi0cHQl4joFt6Az7wS6g3U5Jpzv9wr0C21eZm1Hb0ivacCK1jZcRnshFxZk\/nKnZIHKtpOCvvHA0sPKCMagFgRWTWcOsBR8vNh+BwamkFLvOPWJsqmzYrdVB9SNAHJaqZwLF8q8GUo9KomU4iq2oq4yKKgK\/wBShnpycGCx5icBN5w+mfrDlUoRei76rxUw5RSsg6fuR2qZhujhGeBB6pbGG8bNgWEGiHkFb1Rh8qAXkvsUcS2SaU26TaCy5lDtWBeq5lMmqfmW7FSLvEQKU0SzRZzMHOWOhXdgjTGKqADFHb2lsQIpTewTVsVQrHK3ngOiYIQ0KhjPQZbAENsGwXvUBlSLWlPqsvABt6fMwmTFRUvktFfaWSdw0+cJC61Q+OMuPtCoppC0MKNI6qWGUK2FkYWNQeah5y+ULBaHmH6wWbcyyf0sYM1OZp3D7odCEtbYGeD3gdV2azGBRmXUJhWBec+salH5pS+zbcrqKtnjiDfARToFF9iJyovJ7YrBlL6VUr5lhoZO8KNseFehxBu1RhbS2fwoIsixsPqQIWpF1sU+kKxgq4MfhN2qK26OPLmcB5dM03QPWATdIINl4D1Sr81LozX4lekzCdNLpfhEq7C7kdq\/ETW0IIOyqaIjBJsNK4lseS0o2ccxiUCF6UpKQOw49YpqqEqWLdKyibLO0YHpF6LJmXJkU\/OWVLcJvb9IFtoIwiuq+ssIOF2VnpASJpo2HFZS+8riLnjh3zKDXl\/Mu5FyvU9hNkMaDZ1i0pimGUtdRxpjl4hyQZDp4MwnfUQEkDkOkQVKt1AFYsKpTpnUSgVEWRliucOey4YQJhgGtlp30XOuNFmBaDlXOSJq2zpvADaoVV0JbmZ1HiPOEtBwFt1dk4C1+5peJbIZ10C0h1L53KP4cGQ4u+mb1upkXg0LKq6CTiIAZlxjG4JZbrGYwbRZgDItGVBOcHC66+o4\/UJRdYC6U5FoKBdAK7JZyEeVp2Zi7q43vEQAzLjGNwSy3WMxg2izAGRaMqCc4OF119Rx+oSi6wF1jElDYDnndXPlKIooPwQ\/bGXFyk1IGHDhKXdTNrWkuKbIaChiWt1tlQiYHUNznQ6eHJQjmxVWzVtmlKxASVw07aqLqrnJExQaAmDI6nxiZqtxvHM8SuWVLyhaWO0WLYbyLJks1dgj71uokaFGhPIK3ZQO\/iWu++dwZdi5E0DNFYe0eBAmBThfNuzrvw2ezLxNSb4\/8AwzZGu4\/FLZ\/JSpuYUl+dn4gXONvTlV8sShXVUFXaiEqU4Ct6gR0RZyGIBjDB0UUcQ+dgXg+Rb7zrkpchq1Q3jpClQly4yYrDb0oKcVVPNZhxmnB84cvp0iO9Ra4rBCzVo5zxGjk7wwdNrfOVCyCWQ36wtydVH4QOvwlB1qxrUtwy3hsrsuKq5Y4Us1b75qmBAgiqyNBWIEoa1LFRo50u5t1rUEs7YU5eofOJdzyLbZXeUliXdV\/YhBxru1yww0ZAdjrhm9murGvKKRZBs13H0GCr0S6LVnRrMMInEuUKyWLAvDTpFTLSpUpZ73ScyBpsaoOB0uMpSYwK9EO2JSKRxYIPMJa2eygPa0ddqFWAaFNuItolhWzldajw3YhY+ofWC0FCkXU21n6VByQcqQ9orBsFsKvtFXry2Ie0I4BiB51RPg2KDbzUFhaVt7Ly73KdjKzT9LXUXO+BgtORC4wDWPvNqzAG4KqtjebL9IDU3WNHPqvuRsfV0Kms4VEwy2DrylMNUNXHmGRUKVgGsA9F1mI0C0D6h3Fl\/hQD8sxgQAWU3+I2G1YNB+WUWQVoRnoIjbGx5lAIAxEeF4cueYl7yEAwvXlCHlvqThp+kxjFULWF6o8LdlAHlO02QEU7h+UOv1pV4Va2nniO2suKFPA4xqnBamDopb0lgVFBC7YS\/SNLzouTdUlpqNuQogk9GNw1ZTa5dK11ec4hGi8fSFcc8WDRuvsS5IiMFvNDGmfhaLd7cxItrq\/wAzHsINYY8sMsjAqtDeLLx6Q+RSqTtr0jEh1t2V0jVOJdCJRoRoBc1rcbS7W4otzAwy9QKDw2vJH02lCU76xpqakEmYqIB7DPhmXY02QirAWdIeBXqUKAirFsnFmOIADZi5cFa2oIDRi4dF62vARFhuMFVnecDANRoDKyz8VUqGAKkWDPrBVbBaUVlvJ0OSH2iJS73hOcv8Cq2C0orLeTockPtESl3vCc5eOcoURfccIdYsgQFGnTYDz1cOa8RMAjhtJhcWYlBLqyWtNUYADZi5cFa2omidUFArbmismOYEnJCt\/Upoa89PjYsAtVbOQyF0HZDevpXYE4QZ5CDNAkUqja6NY8Nnsy8TUmo\/FmcIXKI4Y8dJEBguog2esN0keI8EBlyLVJiFaaUuCWpBKGOcMoYYozLMTtBSgYC5gGbgrYzFbXELVjUXJEKqLKUy\/qji0CqDNEfSLepzM1KgaEQipisFNy1LI5zCKViBHIohlohKpAKGvM7J1oZDaoReWM5fpLgJXiwiWahXYzbtEoRbqCLnUmSyA5xKoPEoqVkmrBc1Eq4VFthEZsqXRc7sLryhwFmhL+ZkbgekoR8LF7GJySO11FdY1yxVNkiLXKX4M8RmAgfV7IXMaJUGIxZTigauJzLqIIagDgZhLFBDZZcOZIl7n8nhmE76iAkgch0iCpVuoArFhVKdM6iUCoiyMsVzhz2XDCBMMA1stO+i51xoswLQcq5yRNW2dN4AbVCquhLczOo8R5wloOAtursnAWv3NLxLZDOugWkOpfO5R\/DgyHF30zet1Mi8GhZVXQScRADMuMY3BLLdYzGDaLMAZFoyoJzg4XXX1HH6hKLrAXSnItBQLoBXZLOQjytOzMXdXG94iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAusYkobAc87q58pRFFB+CH7Yy4uUmpAw4cJS7qZta0lxTZDQUMS1utsqETA6huc6HTw5KEc2Kq2ats0pWICSuGnbVRdVc5ImKDQEwZHU+MTNVuN45niVyypeULSx2ixbDeRZMlmrsEfet1EjQo0J5BW7KB38S133zuDLsXImgZorD2jwIEwKcL5t2dd+Gz2ZeJqT7Dt\/gUFFYtnhhMI2hFiqBLmSXSFydIowV7SwGMy6ljwCyQ85Uwr84FyY5mUcBFygnRkQwh6QvCYsQpCCvMxxyzeaY1a5g3ksLi19SFQLOpDbZOhEWPB7nMzxguXllZnE9CNIeG4F+sxSYDvY6I+02ZrxfOkI5i0zADA86icuInMOJ9pqpG5mbggxcW48OEhO4pMZdAEOkYyN8RvVwtswusypdRDRArQTdTBplUxA8ITARCXcsMYmS2GYzjJFycsZoFFmE\/nN42QD4YlokiM5TiFbdwGE3qnBHOYWydgn5+CZdjTZCKsBZ0h4FepQoCKsWycWY4gANmLlwVraggNGLh0Xra8BEWG4wVWd5wMA1GgMrLPxVSoYAqRYM+sFVsFpRWW8nQ5IfaIlLveE5y\/wKrYLSist5OhyQ+0RKXe8Jzl45yhRF9xwh1iyBAUadNgPPVw5rxEwCOG0mFxZiUEurJa01RgANmLlwVraiaJ1QUCtuaKyY5gSckK39Smhrz0+NiwC1Vs5DIXQdkN6+ldgThBnkIM0CRSqNro1jw2ezLxNSfcdv8CQSLFi1EyxgGVDUUQDAQrTFxQwazLA8RBp5gDKNYlF01OFBFJcJjASy1Yl8iFJGoYbjRO8pnqQ2qYwVuAqJl0uMQvCXqBqJfAlFGXpEy611izJLrklbbKUErKVUYrZ5jGOsRLGGt4llPdDatsLV8RjVlmgxG5Iuooja1geLhspMXiSrh8FC4ARnM8qcuHWSgslkB4XyOZluvmjbSludQzDKLg3ZESPORDcIwxDKQbI0USwAm97gapAIAl7RzL68SEuozODRuEBADRMzhOIH0NxGEVlBKncNSytWjGGAuWHV4\/J4ZhO+ogJIHIdIgqVbqAKxYVSnTOolAqIsjLFc4c9lwwgTDANbLTvoudcaLMC0HKuckTVtnTeAG1QqroS3MzqPEecJaDgLbq7JwFr9zS8S2QzroFpDqXzuUfw4Mhxd9M3rdTIvBoWVV0EnEQAzLjGNwSy3WMxg2izAGRaMqCc4OF119Rx+oSi6wF0pyLQUC6AV2SzkI8rTszF3VxveIgBmXGMbgllusZjBtFmAMi0ZUE5wcLrr6jj9QlF1gLrGJKGwHPO6ufKURRQfgh+2MuLlJqQMOHCUu6mbWtJcU2Q0FDEtbrbKhEwOobnOh08OShHNiqtmrbNKViAkrhp21UXVXOSJig0BMGR1PjEzVbjeOZ4lcsqXlC0sdosWw3kWTJZq7BH3rdRI0KNCeQVuygd\/Etd987gy7FyJoGaKw9o8CBMCnC+bdnXfhs9mXiakkF7YJTwp6Tsw6M7Ph2QvtnKQqq2EdVFGoRqErYGaW4dWIZvEQURGwuDMwXQ9ZTioaSaEJdANkDoZZckUhiAL1QiVohz6qDUtZuwi2q\/LGcKhVVuIYXLvYXFGtdoZbga9kIMkMs2mVVKhWC14EodMCK8sVI2lpnSbNVDC0uU7GXcATJGYtUtSgOUrcWzKClXAfOd0UbpnQpYykyAhDykOFZXjCctiUpJXxGcRgwVDUSwIRuCNVGpjc8wRDHMWOowdJacwYVzrLRCu0bKJY3MonEREUVXDADcSYok6ga0gUJYV1eYt4sgNnfSGIRStYd0sd5bL7XwzLsabIRVgLOkPAr1KFARVi2TizHEABsxcuCtbUEBoxcOi9bXgIiw3GCqzvOBgGo0BlZZ+KqVDAFSLBn1gqtgtKKy3k6HJD7REpd7wnOX+BVbBaUVlvJ0OSH2iJS73hOcvHOUKIvuOEOsWQICjTpsB56uHNeImARw2kwuLMSgl1ZLWmqMABsxcuCtbUTROqCgVtzRWTHMCTkhW\/qU0NeenxsWAWqtnIZC6Dsgl033VKmrk8JfmFB0pWyh9\/DZ7MvE1JFXX9IBxAPEvpmJiYmJfaPBOBLHMsbiOmcxYLbHcxfWC6zuS47jYMJVVZLXUYCIddjEKimVecRHWhGjwqWnSBkXiNjVpZ2SvSJGDCCQKGH0j5y9pXAuKEQsjDFZu0DuGBANYc0S08Sh1hQVAYeJdwJausKRaAGVsVoyxsiTco4aAOCUY8yXSr3NwkOlBebhMBjtEdKU4iiusV6xuZg+BzG4HLpM68RpaQ4pEjGhhzUB4O8L2txWn0RWq9YKemLLG5TBuLCmOReyd6AlXXeAciEXuFU5g2n1QU4lxQ7jE8vn5+CYTvqICSByHSIKlW6gCsWFUp0zqJQKiLIyxXOHPZcMIEwwDWy076LnXGizAtByrnJE1bZ03gBtUKq6EtzM6jxHnCWg4C26uycBa\/c0vEtkM66BaQ6l87lH8ODIcXfTN63UyLwaFlVdBJxEAMy4xjcEst1jMYNoswBkWjKgnODhddfUcfqEousBdKci0FAugFdks5CPK07Mxd1cb3iIAZlxjG4JZbrGYwbRZgDItGVBOcHC66+o4\/UJRdYC6xiShsBzzurnylEUUH4IftjLi5SakDDhwlLupm1rSXFNkNBQxLW62yoRMDqG5zodPDkoRzYqrZq2zSlYgJK4adtVF1VzkiYoNATBkdT4xM1W43jmeJXLKl5QtLHaLFsN5FkyWauwR963USNCjQnkFbsoHfxLXffO5fQ1xOaHMB\/qTDXV6D\/HN\/pXhs9mXiakrBY80p6y3WW9Zb1lvWIGGPWnfh1I3KCNxA8Zrpa5eok1N8xggKxDCuiB2biaDINWYi1pCaMQNsCQ7Vw6jEvKUmsEMWl0AYaIMQxClGupWGIU+kfq8SZNwJRfggzUbgTokY3Lo5dQVu98QAdo6fhLTKCmDUsmJkzAYJgzE6Mu8xXkgTcNyRTglepYlkzEkvLS0MyoELRgXqIIOZdZjQt3LjFZIGSUEV1TQCxnZ6SqqicygFBpW5SNrLKCDlCHE748mpqFRacy3jMXriYOIt4mW2INy7yPy8Ey7GmyEVYCzpDwK9ShQEVYtk4sxxAAbMXLgrW1BAaMXDovW14CIsNxgqs7zgYBqNAZWWfiqlQwBUiwZ9YKrYLSist5OhyQ+0RKXe8Jzl\/gVWwWlFZbydDkh9oiUu94TnLxzlCiL7jhDrFkCAo06bAeerhzXiJgEcNpMLizEoJdWS1pqjAAbMXLgrW1E0TqgoFbc0VkxzAk5IVv6lNDXnp8bFgFqrZyGQug7IJdN91Spq5PCX5hQdKVsoffw2ezLxNSQF0\/SUg083\/O0v1inhdblnEvO+ANSkaiDA5TqkDTwRr2Oql0VFVUraiWK0YqzAIZ0I2LOZK4p1GzAB0iSjG9AzCrrAKEF2GGH9cxwhWSJvolt43GZg8VxHcXiVbcolMaFNREoJRdwsbIsCxhKgMsgXHgdF4QjupSxbGI3UsmJcI3xKm4ZBpmGO6AuNvERvG5mMCLt5lw6lKiBzzDEMo4JSDo3H10h\/SCwRWrqLRqXeUWb8BdxthuIWYwgT7D8vBMJ31EBJA5DpEFSrdQBWLCqU6Z1EoFRFkZYrnDnsuGECYYBrZad9FzrjRZgWg5Vzkiats6bwA2qFVdCW5mdR4jzhLQcBbdXZOAtfuaXiWyGddAtIdS+dyj+HBkOLvpm9bqZF4NCyqugk4iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAulORaCgXQCuyWchHladmYu6uN7xEAMy4xjcEst1jMYNoswBkWjKgnODhddfUcfqEousBdYxJQ2A553Vz5SiKKD8EP2xlxcpNSBhw4Sl3Uza1pLimyGgoYlrdbZUImB1Dc50OnhyUI5sVVs1bZpSsQElcNO2qi6q5yRMUGgJgyOp8YmarcbxzPErllS8oWljtFi2G8iyZLNXYI+9bqJGhRoTyCt2UDv4lrvvncvoa4nNDmA\/1Jhrq9B\/jm\/0rw2ezLxNSQe1wSiUEsldU7k7kOWJ1xlSLZUCtyyNMSpmUypTxAOYtamW4Yoi4qXiNwlphQgi1iDI8UbkBljbhgYtwDLmGOBMoy66wMoIvmVGQh4uINaibuLSYMMyo0g3\/AIBqYSJAZqQQQKCMykWS01ApYlGiWzMWXBl9Z0ILhNYE4hEtBGYuFigw95QnhXwgNTBdxCuMXcrIRt+Ywycw4jzN45hCdYcOswYIkGpWoqjA6jBcZS2lHU+38CZdjTZCKsBZ0h4FepQoCKsWycWY4gANmLlwVraggNGLh0Xra8BEWG4wVWd5wMA1GgMrLPxVSoYAqRYM+sFVsFpRWW8nQ5IfaIlLveE5y\/wKrYLSist5OhyQ+0RKXe8Jzl45yhRF9xwh1iyBAUadNgPPVw5rxEwCOG0mFxZiUEurJa01RgANmLlwVraiaJ1QUCtuaKyY5gSckK39Smhrz0+NiwC1Vs5DIXQdkEum+6pU1cnhL8woOlK2UPv4bPZl4mpJXnIxSYE0f\/HZY4YR54QYZYZgZZZZ4DMX3\/5YYpwwQxhpgBqYrv8AxljjwGv89ttscv8A66y5yzxaYHr\/AHngjlhjgf8AvLLLHFNgv9NHHlt8DqctuXsLTW+CCARLf8oBABgFrwx4PVYuGY9yv0oVW3YHc8UwMp\/xjllgc5bfjjli2\/DFLplfj1nlv+MMMwAbIipTj4wQTUv93fuDwmE76iAkgch0iCpVuoArFhVKdM6iUCoiyMsVzhz2XDCBMMA1stO+i51xoswLQcq5yRNW2dN4AbVCquhLczOo8R5wloOAtursnAWv3NLxLZDOugWkOpfO5R\/DgyHF30zet1Mi8GhZVXQScRADMuMY3BLLdYzGDaLMAZFoyoJzg4XXX1HH6hKLrAXSnItBQLoBXZLOQjytOzMXdXG94iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAusYkobAc87q58pRFFB+CH7Yy4uUmpAw4cJS7qZta0lxTZDQUMS1utsqETA6huc6HTw5KEc2Kq2ats0pWICSuGnbVRdVc5ImKDQEwZHU+MTNVuN45niVyypeULSx2ixbDeRZMlmrsEfet1EjQo0J5BW7KB38S133zuX0NcTmhzAf6kw11eg\/wAc3+leGz2ZeJqTTsAC1mr95KQaQaQaSaSaSQ\/5nOcxpBpBrBrz\/wDljbTbbbTaaaabbbbbbTSSSSTSTTTTzzzyyyyyiijDDDCM1BEcoXeEOKKOLmqSmKyVE1JJSeAwoSTCqDaD+iFQV8CC6XRsK1fSJQUpL\/owQyyyw8SCackgT\/6BBhhhhhh5xx5BBBBhBBRRBRBGpjcOY9Ka8Ey7GmyEVYCzpDwK9ShQEVYtk4sxxAAbMXLgrW1BAaMXDovW14CIsNxgqs7zgYBqNAZWWfiqlQwBUiwZ9YKrYLSist5OhyQ+0RKXe8Jzl\/gVWwWlFZbydDkh9oiUu94TnLxzlCiL7jhDrFkCAo06bAeerhzXiJgEcNpMLizEoJdWS1pqjAAbMXLgrW1E0TqgoFbc0VkxzAk5IVv6lNDXnp8bFgFqrZyGQug7JU65OEkGyAWER4\/gHViBs8Lvnw2ezLxNSfcdiZmfCiUSiUEuXLl\/4RoItGVfSe4P1PYH6nsD9T2R+p7o\/U90fqeyP1PZH6nuj9T3R+p7o\/UPdH4nuj9T3R+p7o\/U94fqe8P1PcH6nuD9T3D+p7h\/U9g\/qKe5+09o\/qe2f1PZP6nsn9T2b+p7N\/U9m\/qezf1PZv6nun9T3b+p7t\/U92\/qe7f1Pdv6nu39T3T+p7t\/U90\/qAjmE83kA+SEC59z2mL2HpE+5+0Lfc+kJ7D7T2x+o+2PxCipBlf+ETrFdfB7B18CVgggQJXiGUTHg2gf+FeJX+aJRKJRx\/gVD7TnwTCd9RASQOQ6RBUq3UAViwqlOmdRKBURZGWK5w57LhhAmGAa2WnfRc640WYFoOVc5ImrbOm8ANqhVXQluZnUeI84S0HAW3V2TgLX7ml4lshnXQLSHUvnco\/hwZDi76ZvW6mReDQsqroJOIgBmXGMbgllusZjBtFmAMi0ZUE5wcLrr6jj9QlF1gLpTkWgoF0ArslnIR5WnZmLurje8RADMuMY3BLLdYzGDaLMAZFoyoJzg4XXX1HH6hKLrAXWMSUNgOed1c+Uoiig\/BD9sZcXKTUgYcOEpd1M2taS4pshoKGJa3W2VCJgdQ3OdDp4clCObFVbNW2aUrEBJXDTtqouquckTFBoCYMjqfGJmq3G8czxK5ZUvKFpY7RYthvIsmSzV2CPvW6iRoUaE8grdlA7+Ja7753KQNCAjzOrA1XOszB6VDT8U9GK8Nnsy8TUmvscS2ASeSRvO9KgHytgUpfNjH6ijQoBIuC9Nces3PhALrQt11\/4JPJKKmHjAfWDMzlkFwLHGpUTV0AO6PqPpF4KkIpu6B9Y49XcFbSgvji4IyTSS5HSe9QnKQNDFxYLjX4hl1j+sq9KDWH6wgtV9YKTYS2plbgMd5eW6tgCTuM7jO4zuM7jO4zuM7jO4zuM7jO4zuM7jO4zuM7jO4zuM7jO4zuM7jO4zuM7jO4zuM7jO4zuM7jO4zuM7jO4zuM7jO4zuM7jBVBbGD3nOAOgVBg1LessZ6Y8DF82+0t6ytL5Ch2PmW9Zb1lvWdGQ4RhiCgoDzRA8Ptfyj7qXnwNgooE8YjL1eZnE0cqhzHQ8rwawE0eov+bY15+VxiLeBai8pVV5ovbyOqYNYGcIPVBvzlTb0gz0WaKnKy878yW2FXoQR+1zKe3IpL2EoUChQ4bUZyTGVlHnS7YK30h4ft7zSiVr9T+mEMm\/2qV1xKwXkWiDsuIrtXSqqjrFNUG0G7aMMVjRdXN5hZ4X4lF1w1ddr5eI7eTX4DtD7Dh01zZrpFWBfeFHG2p5BVesp\/hiip3ejUWKOEwj4LSqly5fh7B18Ey7GmyEVYCzpDwK9ShQEVYtk4sxxAAbMXLgrW1BAaMXDovW14CIsNxgqs7zgYBqNAZWWfiqlQwBUiwZ9YKrYLSist5OhyQ+0RKXe8Jzl\/gVWwWlFZbydDkh9oiUu94TnLxzlCiL7jhDrFkCAo06bAeerhzXiJgEcNpMLizEoJdWS1pqjAAbMXLgrW1E0TqgoFbc0VkxzAk5IVv6lNDXnp8bFgFqrZyGQug7JU65OEkGyAWER4\/gHViBs8Lvnw2ezLxNST7HSHmNSQQS9tzxFc1KzxivMGlxZYKMNiVRQeTPNxz1t6TBgA1uGra4Uuh1TGp5WBAEVnC8WEC6C2aWgWfpD0rZYFdo9xCReCo0u5SEyLo1puSje1+kRiKkaSJLyxYQCEYVmF2C6IyyBFOlDiT1EKSq9H3a+qE21kqmg2F\/\/laPOe2dc9m6HhvCzZtgDDas1WZerqASBqwv1iF2XCCkKW1df0mlLS1YsDAU5JdwGqTFXvC3gNkXtOc0hPtfyj77wrAJ0AGshbzhlkwFSoDucXcABFr5UFrfrupng5vivLnyEAv7kYE7LcGV346WlKOY47anOm88bqzULIzcAoDGtcOOGAuQ306sg3ksT8lO6AZ6D7EJcn1kKB40+yDeRh1LD05zlxB20rF20Eqz\/GWwkKwvOLIMi2Iqt1m1WVr6xq4CSI2NM8Jwcvk11H1f5r9ueHVWENMxCXKlyWm\/fZ9KiR1o3iQedmPWZ4PrFbKtT\/ioITtAWG4bxrOeY0JW2tq4vfHaMjMFgRrco9fOKG4KaBZsH7S\/C5fh9p+XgmE76iAkgch0iCpVuoArFhVKdM6iUCoiyMsVzhz2XDCBMMA1stO+i51xoswLQcq5yRNW2dN4AbVCquhLczOo8R5wloOAtursnAWv3NLxLZDOugWkOpfO5R\/DgyHF30zet1Mi8GhZVXQScRADMuMY3BLLdYzGDaLMAZFoyoJzg4XXX1HH6hKLrAXSnItBQLoBXZLOQjytOzMXdXG94iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAusYkobAc87q58pRFFB+CH7Yy4uUmpAw4cJS7qZta0lxTZDQUMS1utsqETA6huc6HTw5KEc2Kq2ats0pWICSuGnbVRdVc5ImKDQEwZHU+MTNVuN45niVyypeULSx2ixbDeRZMlmrsEfet1EjQo0J5BW7KB38S133zuUgaEBHmdWBqudZmD0qGn4p6MV4bPZl4mpPtO3+3\/Ny5cXGxBNManb93nO3J2ZOxJ2JO1J2pDoSdiTsSdiTsSdiTsSdiTtSdqTtSdiTsSdiTtyduTsSdiTsSdmTtyduTtyC8SdiTsSdiTsSdiTsSdiTtyduTtydv1oea2Jq1WwKslfau1T594JD\/10fLoH\/qjF+1DFFkJDDZpirG9V1VsXEJ9r+UfdSsII4fEeCy8RmZlhiDnYzWcf6fsZStp0tERONuUPKvhXhUqVKlSpUqVKlSpUqfafl4Jl2NNkIqwFnSHgV6lCgIqxbJxZjiAA2YuXBWtqCA0YuHRetrwERYbjBVZ3nAwDUaAyss\/FVKhgCpFgz6wVWwWlFZbydDkh9oiUu94TnL\/AqtgtKKy3k6HJD7REpd7wnOXjnKFEX3HCHWLIEBRp02A89XDmvETAI4bSYXFmJQS6slrTVGAA2YuXBWtqJonVBQK25orJjmBJyQrf1KaGvPT42LALVWzkMhdB2Sp1ycJINkAsIjx\/AOrEDZ4XfPhs9mXiak2P03i1sC7P\/nWWW2WWW2222musuMccMMMMC\/8AxssssMMMOMuMMOOOMOOOGMssMMMMMMccMOsssssIc+BjgczDMYYzxljDCaJ9IDb7fqJal6tstMsNEZYcsxcxmhzxCOH+HDDHGWB\/+jLLLLDnvnjDLrLLLLtlllhhhmLaIjk9\/QjwmE76iAkgch0iCpVuoArFhVKdM6iUCoiyMsVzhz2XDCBMMA1stO+i51xoswLQcq5yRNW2dN4AbVCquhLczOo8R5wloOAtursnAWv3NLxLZDOugWkOpfO5R\/DgyHF30zet1Mi8GhZVXQScRADMuMY3BLLdYzGDaLMAZFoyoJzg4XXX1HH6hKLrAXSnItBQLoBXZLOQjytOzMXdXG94iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAusYkobAc87q58pRFFB+CH7Yy4uUmpAw4cJS7qZta0lxTZDQUMS1utsqETA6huc6HTw5KEc2Kq2ats0pWICSuGnbVRdVc5ImKDQEwZHU+MTNVuN45niVyypeULSx2ixbDeRZMlmrsEfet1EjQo0J5BW7KB38S133zuUgaEBHmdWBqudZmD0qGn4p6MV4bPZl4mpIiLcv6a\/\/ANaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgWVQ9ep1DwmXY02QirAWdIeBXqUKAirFsnFmOIADZi5cFa2oIDRi4dF62vARFhuMFVnecDANRoDKyz8VUqGAKkWDPrBVbBaUVlvJ0OSH2iJS73hOcv8Cq2C0orLeTockPtESl3vCc5eOcoURfccIdYsgQFGnTYDz1cOa8RMAjhtJhcWYlBLqyWtNUYADZi5cFa2omidUFArbmismOYEnJCt\/Upoa89PjYsAtVbOQyF0HZL9kNhkspQHkMut53V0RSDjw2ezLxNSTmxL2mploEFAg4qRuKyoGGYvJHrd++0rXnkM5hKaghFXSUtIvX2TJFJt1e3xMxQ6BB0U6q\/5EHYioM2r5om5HTKV\/wBEq\/snzKW5+2yj9CI8omXaDrcWck8xKdEy5E40pnVEdo1gC+U3S8lOkjzSv+tl\/HpsR+kzgM+kemQOrwpKSkojUqjDLUCYZa5j0JLHMvzuXcAYGVahlgdIJzF1WIByjmxiHJLVBiG1OZUSlBUOqdedHOilEslEHiINwOmPj0OJbqSnU8KlqHJOhKcJMV2R6xKdSef\/ABVWWlpaH+QecD8\/BMJ31EBJA5DpEFSrdQBWLCqU6Z1EoFRFkZYrnDnsuGECYYBrZad9FzrjRZgWg5Vzkiats6bwA2qFVdCW5mdR4jzhLQcBbdXZOAtfuaXiWyGddAtIdS+dyj+HBkOLvpm9bqZF4NCyqugk4iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAulORaCgXQCuyWchHladmYu6uN7xEAMy4xjcEst1jMYNoswBkWjKgnODhddfUcfqEousBdYxJQ2A553Vz5SiKKD8EP2xlxcpNSBhw4Sl3Uza1pLimyGgoYlrdbZUImB1Dc50OnhyUI5sVVs1bZpSsQElcNO2qi6q5yRMUGgJgyOp8YmarcbxzPErllS8oWljtFi2G8iyZLNXYI+9bqJGhRoTyCt2UDv4lrvvncxyaAkXdrVioSNLLof630eGz2Zf4NSUueYGLIrqT+RgUDxEnDOf5h0h2YQlZrvd8eRBzqlC+15jiPZRZtjpKgUdcWh+5acQpJu8Ugswk581tOHSGv4YbIaZuVWVpyXWGA9c1A9g6Sm\/goYLnASFkJFKVwinR1CCL0FN5sl\/Z+sQ01kw5Tvqqcx4vSIu6qQi6A1qYbRZK\/4Sws1Kp\/Ic3jw0SwLpuglcRW7NRMU2VQn5IE3g4OkvrMXYPyIqtIzRPyotor3lSuGiMYS7WdygrQvID6Sxd6wm+tmIUl3\/wCRdo3bS6+tROz5X\/J53G3LBZqUIUtDoM\/cEI68KruFRdMnYjSfSEBuu4ZSz3nQB3ZgIme8Q+Y9Fl3zLZgecdX0n9h\/wn9lPB+iMY\/MR2DzYpl+qJf9R+dHjIRz+UWai7II34CG2E6IgDWGNEPokdVQYcBMhPslJSXFNSIX+UHVfqlr9k3D7oJt6oB1PONmafJ\/ZRwvrTOA+sDWV6xiz1RuxSR0npKV\/SDNL0bE7APW45VntNKEscJOCmDXTBuFEXIxQyRNMykaQLAXUeq9InU9J2oNx9ony9Icg+ktl6fn4Zl2NNkIqwFnSHgV6lCgIqxbJxZjiAA2YuXBWtqCA0YuHRetrwERYbjBVZ3nAwDUaAyss\/FVKhgCpFgz6wVWwWlFZbydDkh9oiUu94TnL\/AqtgtKKy3k6HJD7REpd7wnOXjnKFEX3HCHWLIEBRp02A89XDmvETAI4bSYXFmJQS6slrTVGAA2YuXBWtqJonVBQK25orJjmBJyQrf1KaGvPT42LALVWzkMhdB2S\/ZDYZLKUB5DLred1dEUg48Nnsy\/yakxQdVmqW9arKYAupAW2G7xI0ULa1S7ruZ48p7j1luzUlIGoK0mFd\/wg0keHRW23NhNEoKdNB9Ge79EK52B0bKEWJcph9bgu+nQqAZEU3zDexqyB0C4dgmS6w3F07XWOfDNTitZFvJrbUxe4YUV7YmSeyxyhaygxnefBvTYQF3m9\/SafKOhpitfqOvm\/EXpv5hsGDLEuLBHHoC7fpFkN30Ja\/WmaebPvIX1v4jaACmyhee7MyDgtmew+kKYpHfPpBFfaiq0EtMk8oCfcQqxnzijoiXJPKMX5Av9l7FnS0C7E839mSfOE\/s0W\/WZxepnL17rKKgnn\/yHwh5iHOmVpBQSm4toILgmA0l2gjYBrEa0RFwhEdb4Q+ZyfZMTjGMudokq8cKvfnNYjt\/2Bc\/33gso84VSzyjuryX+wFoz1ZxWsuOMVsuVwzq\/EwFPy\/5GH4Z\/Jf8AHP5B\/wAP\/kzfh\/8AIvgr8v8AkszZ8j+RW+z8TZ+z6RxPsf8AJrDOES5zIplRv0xZwMcvC6xJSPflNZ7PpGGPZ9Ihb7PpKlHo\/wDIZRTt8Rfx\/qLfnwmE76iAkgch0iCpVuoArFhVKdM6iUCoiyMsVzhz2XDCBMMA1stO+i51xoswLQcq5yRNW2dN4AbVCquhLczOo8R5wloOAtursnAWv3NLxLZDOugWkOpfO5R\/DgyHF30zet1Mi8GhZVXQScRADMuMY3BLLdYzGDaLMAZFoyoJzg4XXX1HH6hKLrAXSnItBQLoBXZLOQjytOzMXdXG94iAGZcYxuCWW6xmMG0WYAyLRlQTnBwuuvqOP1CUXWAusYkobAc87q58pRFFB+CH7Yy4uUmpAw4cJS7qZta0lxTZDQUMS1utsqETA6huc6HTw5KEc2Kq2ats0pWICSuGnbVRdVc5ImKDQEwZHU+MTNVuN45niVyypeULSx2ixbDeRZMlmrsEfet1EjQo0J5BW7KB38S133zuY5NASLu1qxUJGll0P9b6PDZ7Mv8AJqTFF9QprY7E8mYS0sSHJXf3YQg+ij1jV4Oh+49wvGBfehjNZAu8AsOsXpJHKwyWDVvnCxtnlCo4d2WkvYgQlVQJtQ0KZvBcvyuxN7zhs78vOLrEp5O\/HHn5S0FaJcRRjmILfdjmivW\/rH3XK6HKfV8Hhe5mDN+HYV1iSo44LjjhpaqzGEhoLKhTna6eM94QFF4YlasOKE6SksObbgxRK4I1TgVLxbu7qBIKBui4xYU0YlQropCC28GniUihdlQbUMOahCyLQYlDtalFMMIoWpxM9zLLgzKSwTdPBNze9UwRO8e0DtZZDbiLFBAUziUdZ2MV1qAnFxt7QBKViO1BjlgAvbCaumWLg22g9pQGCASTZsCGzF6WUbcZ4cVJGVoWUMdZyss3mIlwe8KdGKwoeUyq75TjCHITAB9IqZPpK2xvpBVRp0ilWMQAqsFxO8olI00uXoCWbqWbUirFQroCAbqDwIHJOjJY1LOvhaWx86g1puL6TL2gZ3EBmUoW5CABRWX5PCZdjTZCKsBZ0h4FepQoCKsWycWY4gANmLlwVraggNGLh0Xra8BEWG4wVWd5wMA1GgMrLPxVSoYAqRYM+sFVsFpRWW8nQ5IfaIlLveE5y\/wKrYLSist5OhyQ+0RKXe8Jzl45yhRF9xwh1iyBAUadNgPPVw5rxEwCOG0mFxZiUEurJa01RgANmLlwVraiaJ1QUCtuaKyY5gSckK39Smhrz0+NiwC1Vs5DIXQdkv2Q2GSylAeQy63ndXRFIOJTNnsy8TUl64VtJ9d7z2h+57Q\/c9ofud49neewP3D2h+Z3PY7w9ofmPF7DvPYH7nT9h3nU9h3nc9jvOy9neewP3PaH7ntD9z2h+57Q\/c9ofue0P3PeH7ntD9z2h+57Q\/c9ofue0P3PaH7ntD9z2h+48HsO8PYH5nd9jvAd+w7z2h+57Q\/c9ofudYezvHg9h3ntD9zo+w7z2h+523s7x6x7O89ofuAPsPWewP3O57HeU49h3nV9h3j0e47w5PYd57A\/cy8Hs5j0ns7yzk9neFvB7OZl37Hed49nedB9jvOr7DvHq+x3nuD9w6nsd4i8ew7xYyns7wHsPvCjKezvO57HeVaT2d5k9h6zL7D1jhU9neAbfY7x4E9nee0P3OSXs6zPj2HeKew+8B7D7zFkezvDk9h3mXfsd4nXsO8GuSfZzAXDT2czJsr2cxXn2O8U59jvCrv2O8wbL9nMzcHs5nvD9z2h+57A\/c7T2d57w\/c9ofuewP3PYH7nUfY7x4H2O8p7D8z2h+5kv2HnFyqezvDqex3jd7D1gwowJ6HeJzR5kts5jDrKcpC9TnEHWMIqwB5AsbxcHRyzapaC0S1mI04E8jeA0bRq5kSQCXDwW+duZQUxYRuKFRu3TW46Vw5CtA5SrtCoOOm4DIEVhkAgutW5VsUABOLgpXY7da\/UdrPCAWVMKF30qAuoqawpXNYQ6BBmomVJWDGKzhACLGwSCc7bbnEDuQLDBtTTLYWajRJFYgERhGZc6gLqKmsKVzWEOgQZqJlSVgxis4QAixsEgnO225xA+l2NM22uVxB5Z3UZsvX7TjB1JK8lzwHv0h3lfSscpCjN0Ld4UYU2YgSNACudMFhoB5ZahIU+YjJgCzLdfXStt6lZOea3BYdZN6ltB6AMwNFO7yuaZZ2mxAX0ZZMFddxDktkFDUTWjGKzhBdEQgOGC7yLmWRmMjCyO8I9cBLYAtTQA7ExyAV4Q6mzr0+lASBAA9gAHwpL9l5UUrREeH\/+DypUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpX\/pKlSpUqVKlSpUqUdCtoFmi9+hzP\/Z", + "ShadowAttribute": [] + } + ], + "ShadowAttribute": [], + "RelatedEvent": [ + { + "Event": { + "id": "845", + "date": "2018-10-10", + "threat_level_id": "3", + "info": "OSINT - Threat Spotlight: Panda Banker Trojan Targets the US, Canada and Japan", + "published": true, + "uuid": "5bbe09c9-9040-4415-bd25-45b7950d210f", + "analysis": "2", + "timestamp": "1550653998", + "distribution": "3", + "org_id": "1", + "orgc_id": "2", + "Org": { + "id": "1", + "name": "ORGNAME", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Orgc": { + "id": "2", + "name": "CIRCL", + "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f" + } + } + }, + { + "Event": { + "id": "251", + "date": "2018-04-26", + "threat_level_id": "2", + "info": "OSINT - Analyzing Operation GhostSecret: Attack Seeks to Steal Data Worldwide", + "published": true, + "uuid": "5ae2129e-15b4-41e9-9428-4f1e02de0b81", + "analysis": "2", + "timestamp": "1550506954", + "distribution": "3", + "org_id": "1", + "orgc_id": "2", + "Org": { + "id": "1", + "name": "ORGNAME", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Orgc": { + "id": "2", + "name": "CIRCL", + "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f" + } + } + }, + { + "Event": { + "id": "885", + "date": "2018-03-13", + "threat_level_id": "3", + "info": "OSINT - Gozi ISFB Remains Active in 2018, Leverages \"Dark Cloud\" Botnet For Distribution", + "published": false, + "uuid": "5aa7b639-62d8-46e6-be6c-4db8950d210f", + "analysis": "0", + "timestamp": "1550654228", + "distribution": "3", + "org_id": "1", + "orgc_id": "2", + "Org": { + "id": "1", + "name": "ORGNAME", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Orgc": { + "id": "2", + "name": "CIRCL", + "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f" + } + } + }, + { + "Event": { + "id": "1082", + "date": "2018-01-29", + "threat_level_id": "3", + "info": "OSINT - VERMIN: Quasar RAT and Custom Malware Used In Ukraine", + "published": true, + "uuid": "5a6f379d-3854-4457-949e-41bb950d210f", + "analysis": "2", + "timestamp": "1550655231", + "distribution": "3", + "org_id": "1", + "orgc_id": "2", + "Org": { + "id": "1", + "name": "ORGNAME", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Orgc": { + "id": "2", + "name": "CIRCL", + "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f" + } + } + }, + { + "Event": { + "id": "483", + "date": "2018-01-25", + "threat_level_id": "3", + "info": "OSINT - Dark Caracal Cyber-espionage at a Global Scale", + "published": true, + "uuid": "5a69ed26-44c8-423c-a8dc-4f7b950d210f", + "analysis": "2", + "timestamp": "1550652819", + "distribution": "3", + "org_id": "1", + "orgc_id": "2", + "Org": { + "id": "1", + "name": "ORGNAME", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Orgc": { + "id": "2", + "name": "CIRCL", + "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f" + } + } + }, + { + "Event": { + "id": "161", + "date": "2017-12-14", + "threat_level_id": "3", + "info": "OSINT - Zeus Panda Banking Trojan Targets Online Holiday Shoppers", + "published": true, + "uuid": "5a390de6-4a58-4a19-89fb-4620950d210f", + "analysis": "2", + "timestamp": "1550506663", + "distribution": "3", + "org_id": "1", + "orgc_id": "2", + "Org": { + "id": "1", + "name": "ORGNAME", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Orgc": { + "id": "2", + "name": "CIRCL", + "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f" + } + } + }, + { + "Event": { + "id": "953", + "date": "2017-12-14", + "threat_level_id": "3", + "info": "OSINT - Zeus Panda Banking Trojan Targets Online Holiday Shoppers", + "published": true, + "uuid": "5a8ab58a-213c-409a-97af-4eb5950d210f", + "analysis": "2", + "timestamp": "1550654740", + "distribution": "3", + "org_id": "1", + "orgc_id": "2", + "Org": { + "id": "1", + "name": "ORGNAME", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Orgc": { + "id": "2", + "name": "CIRCL", + "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f" + } + } + }, + { + "Event": { + "id": "453", + "date": "2014-10-28", + "threat_level_id": "2", + "info": "OSINT - Operation SMN (Novetta)", + "published": true, + "uuid": "544f8aa7-9224-46ad-a73f-30f9950d210b", + "analysis": "2", + "timestamp": "1550652720", + "distribution": "3", + "org_id": "1", + "orgc_id": "2", + "Org": { + "id": "1", + "name": "ORGNAME", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Orgc": { + "id": "2", + "name": "CIRCL", + "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f" + } + } + } + ], + "Galaxy": [ + { + "id": "32", + "uuid": "c1dc03b2-89b3-42a5-9d41-782ef726435a", + "name": "Election guidelines", + "type": "guidelines", + "description": "Universal Development and Security Guidelines as Applicable to Election Technology.", + "version": "1", + "icon": "map", + "namespace": "misp", + "GalaxyCluster": [ + { + "id": "4135", + "collection_uuid": "5079fa10-1df3-43f8-b0bf-cea7d342f5e1", + "type": "guidelines", + "value": "Defacement, DoS or overload of websites or other systems used for publication of the results", + "tag_name": "misp-galaxy:guidelines=\"Defacement, DoS or overload of websites or other systems used for publication of the results\"", + "description": "Defacement, DoS or overload of websites or other systems used for publication of the results", + "galaxy_id": "32", + "source": "Open Sources", + "authors": [ + "NIS Cooperation Group" + ], + "version": "1", + "uuid": "", + "tag_id": "723", + "meta": { + "date": [ + "March 2018." + ], + "kill_chain": [ + "example-of-threats:campaign\/public-communication | media\/press" + ], + "refs": [ + "https:\/\/www.ria.ee\/sites\/default\/files\/content-editors\/kuberturve\/cyber_security_of_election_technology.pdf" + ] + } + }, + { + "id": "4122", + "collection_uuid": "650642c7-ab31-4844-a69f-22294925edeb", + "type": "guidelines", + "value": "Leak of confidential information", + "tag_name": "misp-galaxy:guidelines=\"Leak of confidential information\"", + "description": "Leak of confidential information", + "galaxy_id": "32", + "source": "Open Sources", + "authors": [ + "NIS Cooperation Group" + ], + "version": "1", + "uuid": "", + "tag_id": "724", + "meta": { + "date": [ + "March 2018." + ], + "kill_chain": [ + "example-of-threats:campaign | campaign-IT" + ], + "refs": [ + "https:\/\/www.ria.ee\/sites\/default\/files\/content-editors\/kuberturve\/cyber_security_of_election_technology.pdf" + ] + } + }, + { + "id": "4131", + "collection_uuid": "3c817f6f-08f3-4e8c-8d94-e23b823beb8f", + "type": "guidelines", + "value": "Tampering or DoS of communication links uesd to transfer (interim) results", + "tag_name": "misp-galaxy:guidelines=\"Tampering or DoS of communication links uesd to transfer (interim) results\"", + "description": "Tampering or DoS of communication links uesd to transfer (interim) results", + "galaxy_id": "32", + "source": "Open Sources", + "authors": [ + "NIS Cooperation Group" + ], + "version": "1", + "uuid": "", + "tag_id": "725", + "meta": { + "date": [ + "March 2018." + ], + "kill_chain": [ + "example-of-threats:voting | election-technology" + ], + "refs": [ + "https:\/\/www.ria.ee\/sites\/default\/files\/content-editors\/kuberturve\/cyber_security_of_election_technology.pdf" + ] + } + } + ] + }, + { + "id": "30", + "uuid": "1023f364-7831-11e7-8318-43b5531983ab", + "name": "Intrusion Set", + "type": "mitre-intrusion-set", + "description": "Name of ATT&CK Group", + "version": "8", + "icon": "user-secret", + "namespace": "mitre-attack", + "GalaxyCluster": [ + { + "id": "4012", + "collection_uuid": "6a2e693f-24e5-451a-9f88-b36a108e5662", + "type": "mitre-intrusion-set", + "value": "APT1 - G0006", + "tag_name": "misp-galaxy:mitre-intrusion-set=\"APT1 - G0006\"", + "description": "[APT1](https:\/\/attack.mitre.org\/groups\/G0006) is a Chinese threat group that has been attributed to the 2nd Bureau of the People\u2019s Liberation Army (PLA) General Staff Department\u2019s (GSD) 3rd Department, commonly known by its Military Unit Cover Designator (MUCD) as Unit 61398. (Citation: Mandiant APT1)", + "galaxy_id": "30", + "source": "https:\/\/github.com\/mitre\/cti", + "authors": [ + "MITRE" + ], + "version": "12", + "uuid": "", + "tag_id": "726", + "meta": { + "external_id": [ + "G0006" + ], + "refs": [ + "https:\/\/attack.mitre.org\/groups\/G0006", + "https:\/\/www.fireeye.com\/content\/dam\/fireeye-www\/services\/pdfs\/mandiant-apt1-report.pdf", + "http:\/\/cdn0.vox-cdn.com\/assets\/4589853\/crowdstrike-intelligence-report-putter-panda.original.pdf" + ], + "synonyms": [ + "APT1", + "Comment Crew", + "Comment Group", + "Comment Panda" + ] + } + }, + { + "id": "4010", + "collection_uuid": "c47f937f-1022-4f42-8525-e7a4779a14cb", + "type": "mitre-intrusion-set", + "value": "APT12 - G0005", + "tag_name": "misp-galaxy:mitre-intrusion-set=\"APT12 - G0005\"", + "description": "[APT12](https:\/\/attack.mitre.org\/groups\/G0005) is a threat group that has been attributed to China. (Citation: Meyers Numbered Panda)", + "galaxy_id": "30", + "source": "https:\/\/github.com\/mitre\/cti", + "authors": [ + "MITRE" + ], + "version": "12", + "uuid": "", + "tag_id": "727", + "meta": { + "external_id": [ + "G0005" + ], + "refs": [ + "https:\/\/attack.mitre.org\/groups\/G0005", + "http:\/\/www.crowdstrike.com\/blog\/whois-numbered-panda\/", + "https:\/\/www.fireeye.com\/blog\/threat-research\/2014\/09\/darwins-favorite-apt-group-2.html" + ], + "synonyms": [ + "APT12", + "IXESHE", + "DynCalc", + "Numbered Panda", + "DNSCALC" + ] + } + } + ] + }, + { + "id": "12", + "uuid": "d752161c-78f6-11e7-a0ea-bfa79b407ce4", + "name": "Malware", + "type": "mitre-malware", + "description": "Name of ATT&CK software", + "version": "6", + "icon": "optin-monster", + "namespace": "mitre-attack", + "GalaxyCluster": [ + { + "id": "2221", + "collection_uuid": "4bf6ba32-4165-42c1-b911-9c36165891c8", + "type": "mitre-malware", + "value": "ANDROIDOS_ANSERVER.A - S0310", + "tag_name": "misp-galaxy:mitre-malware=\"ANDROIDOS_ANSERVER.A - S0310\"", + "description": "[ANDROIDOS_ANSERVER.A](https:\/\/attack.mitre.org\/software\/S0310) is Android malware that is unique because it uses encrypted content within a blog site for command and control. (Citation: TrendMicro-Anserver)", + "galaxy_id": "12", + "source": "https:\/\/github.com\/mitre\/cti", + "authors": [ + "MITRE" + ], + "version": "11", + "uuid": "", + "tag_id": "728", + "meta": { + "external_id": [ + "S0310" + ], + "mitre_platforms": [ + "Android" + ], + "refs": [ + "https:\/\/attack.mitre.org\/software\/S0310", + "http:\/\/blog.trendmicro.com\/trendlabs-security-intelligence\/android-malware-uses-blog-posts-as-cc\/" + ], + "synonyms": [ + "ANDROIDOS_ANSERVER.A" + ] + } + }, + { + "id": "2246", + "collection_uuid": "fb261c56-b80e-43a9-8351-c84081e7213d", + "type": "mitre-malware", + "value": "BACKSPACE - S0031", + "tag_name": "misp-galaxy:mitre-malware=\"BACKSPACE - S0031\"", + "description": "[BACKSPACE](https:\/\/attack.mitre.org\/software\/S0031) is a backdoor used by [APT30](https:\/\/attack.mitre.org\/groups\/G0013) that dates back to at least 2005. (Citation: FireEye APT30)", + "galaxy_id": "12", + "source": "https:\/\/github.com\/mitre\/cti", + "authors": [ + "MITRE" + ], + "version": "11", + "uuid": "", + "tag_id": "729", + "meta": { + "external_id": [ + "S0031" + ], + "mitre_platforms": [ + "Windows" + ], + "refs": [ + "https:\/\/attack.mitre.org\/software\/S0031", + "https:\/\/www2.fireeye.com\/rs\/fireye\/images\/rpt-apt30.pdf" + ], + "synonyms": [ + "BACKSPACE", + "Lecna" + ] + } + } + ] + } + ], + "Object": [ + { + "id": "165", + "name": "file", + "meta-category": "file", + "description": "File object describing a file with meta-information", + "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", + "template_version": "7", + "event_id": "30", + "uuid": "2ba7f152-381c-470f-a732-792397b424d4", + "timestamp": "1523391192", + "distribution": "5", + "sharing_group_id": "0", + "comment": "", + "deleted": false, + "ObjectReference": [ + { + "id": "43", + "uuid": "5acd1ad7-df04-4155-bc1c-464602de0b81", + "timestamp": "1550506225", + "object_id": "165", + "event_id": "30", + "source_uuid": "2ba7f152-381c-470f-a732-792397b424d4", + "referenced_uuid": "eefb6d88-9cc1-4d65-b266-b2e82a2464b9", + "referenced_id": "166", + "referenced_type": "1", + "relationship_type": "analysed-with", + "comment": "", + "deleted": false, + "Object": { + "distribution": "5", + "sharing_group_id": "0", + "uuid": "eefb6d88-9cc1-4d65-b266-b2e82a2464b9", + "name": "virustotal-report", + "meta-category": "misc" + } + } + ], + "Attribute": [ + { + "id": "5295", + "type": "sha1", + "category": "Payload delivery", + "to_ids": true, + "uuid": "5acd1ad5-d454-4166-aa3a-498d02de0b81", + "event_id": "30", + "distribution": "5", + "timestamp": "1523391189", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "165", + "object_relation": "sha1", + "value": "d63ff86f05b6f2fb86abf0dcd16cd2008fa3c158", + "Galaxy": [ + { + "id": "33", + "uuid": "fa7016a8-1707-11e8-82d0-1b73d76eb204", + "name": "Enterprise Attack - Attack Pattern", + "type": "mitre-enterprise-attack-attack-pattern", + "description": "ATT&CK Tactic", + "version": "5", + "icon": "map", + "namespace": "deprecated", + "GalaxyCluster": [ + { + "id": "4292", + "collection_uuid": "a10641f4-87b4-45a3-a906-92a149cb2c27", + "type": "mitre-enterprise-attack-attack-pattern", + "value": "Account Manipulation - T1098", + "tag_name": "misp-galaxy:mitre-enterprise-attack-attack-pattern=\"Account Manipulation - T1098\"", + "description": "Account manipulation may aid adversaries in maintaining access to credentials and certain permission levels within an environment. Manipulation could consist of modifying permissions, modifying credentials, adding or changing permission groups, modifying account settings, or modifying how authentication is performed. In order to create or manipulate accounts, the adversary must already have sufficient permissions on systems or the domain.\n\nDetection: Collect events that correlate with changes to account objects on systems and the domain, such as event ID 4738. (Citation: Microsoft User Modified Event) Monitor for modification of accounts in correlation with other suspicious activity. Changes may occur at unusual times or from unusual systems. Especially flag events where the subject and target accounts differ (Citation: InsiderThreat ChangeNTLM July 2017) or that include additional flags such as changing a password without knowledge of the old password. (Citation: GitHub Mimikatz Issue 92 June 2017)\n\nUse of credentials may also occur at unusual times or to unusual systems or services and may correlate with other suspicious activity.\n\nPlatforms: Windows\n\nData Sources: Authentication logs, API monitoring, Windows event logs, Packet capture\n\nPermissions Required: Administrator", + "galaxy_id": "33", + "source": "https:\/\/github.com\/mitre\/cti", + "authors": [ + "MITRE" + ], + "version": "4", + "uuid": "", + "tag_id": "716", + "meta": { + "external_id": [ + "T1098" + ], + "kill_chain": [ + "mitre-attack:enterprise-attack:credential-access" + ], + "mitre_data_sources": [ + "Authentication logs", + "API monitoring", + "Windows event logs", + "Packet capture" + ], + "mitre_platforms": [ + "Windows" + ], + "refs": [ + "https:\/\/attack.mitre.org\/wiki\/Technique\/T1098", + "https:\/\/docs.microsoft.com\/windows\/device-security\/auditing\/event-4738", + "https:\/\/blog.stealthbits.com\/manipulating-user-passwords-with-mimikatz-SetNTLM-ChangeNTLM", + "https:\/\/github.com\/gentilkiwi\/mimikatz\/issues\/92" + ] + } + } + ] + } + ], + "ShadowAttribute": [], + "Tag": [ + { + "id": "716", + "name": "misp-galaxy:mitre-enterprise-attack-attack-pattern=\"Account Manipulation - T1098\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + } + ] + }, + { + "id": "5296", + "type": "sha256", + "category": "Payload delivery", + "to_ids": true, + "uuid": "5acd1ad5-0c3c-4e72-8ca1-40d102de0b81", + "event_id": "30", + "distribution": "5", + "timestamp": "1523391189", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "165", + "object_relation": "sha256", + "value": "3208efe96d14f5a6a2840daecbead6b0f4d73c5a05192a1a8eef8b50bbfb4bc1", + "Galaxy": [], + "ShadowAttribute": [] + }, + { + "id": "5297", + "type": "md5", + "category": "Payload delivery", + "to_ids": true, + "uuid": "5acd1ad6-9458-43ed-8bda-48b202de0b81", + "event_id": "30", + "distribution": "5", + "timestamp": "1523391190", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "165", + "object_relation": "md5", + "value": "0997ba7292ddbac1c7e7ade6766ed53c", + "Galaxy": [ + { + "id": "32", + "uuid": "c1dc03b2-89b3-42a5-9d41-782ef726435a", + "name": "Election guidelines", + "type": "guidelines", + "description": "Universal Development and Security Guidelines as Applicable to Election Technology.", + "version": "1", + "icon": "map", + "namespace": "misp", + "GalaxyCluster": [ + { + "id": "4125", + "collection_uuid": "b7eef207-ae5d-472d-bf7c-9f539c2c4bbc", + "type": "guidelines", + "value": "DoS or overload of government websites", + "tag_name": "misp-galaxy:guidelines=\"DoS or overload of government websites\"", + "description": "DoS or overload of government websites", + "galaxy_id": "32", + "source": "Open Sources", + "authors": [ + "NIS Cooperation Group" + ], + "version": "1", + "uuid": "", + "tag_id": "717", + "meta": { + "date": [ + "March 2018." + ], + "kill_chain": [ + "example-of-threats:all-phases | governement-IT" + ], + "refs": [ + "https:\/\/www.ria.ee\/sites\/default\/files\/content-editors\/kuberturve\/cyber_security_of_election_technology.pdf" + ] + } + } + ] + }, + { + "id": "2", + "uuid": "6fcb4472-6de4-11e7-b5f7-37771619e14e", + "name": "Course of Action", + "type": "mitre-course-of-action", + "description": "ATT&CK Mitigation", + "version": "7", + "icon": "chain", + "namespace": "mitre-attack", + "GalaxyCluster": [ + { + "id": "166", + "collection_uuid": "5c49bc54-9929-48ca-b581-7018219b5a97", + "type": "mitre-course-of-action", + "value": "Account Discovery Mitigation - T1087", + "tag_name": "misp-galaxy:mitre-course-of-action=\"Account Discovery Mitigation - T1087\"", + "description": "Prevent administrator accounts from being enumerated when an application is elevating through UAC since it can lead to the disclosure of account names. The Registry key is located HKLM\\ SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\CredUI\\EnumerateAdministrators<\/code>. It can be disabled through GPO: Computer Configuration > [Policies] > Administrative Templates > Windows Components > Credential User Interface: E numerate administrator accounts on elevation. (Citation: UCF STIG Elevation Account Enumeration)\n\nIdentify unnecessary system utilities or potentially malicious software that may be used to acquire information about system and domain accounts, and audit and\/or block them by using whitelisting (Citation: Beechey 2010) tools, like AppLocker, (Citation: Windows Commands JPCERT) (Citation: NSA MS AppLocker) or Software Restriction Policies (Citation: Corio 2008) where appropriate. (Citation: TechNet Applocker vs SRP)", + "galaxy_id": "2", + "source": "https:\/\/github.com\/mitre\/cti", + "authors": [ + "MITRE" + ], + "version": "9", + "uuid": "", + "tag_id": "718", + "meta": { + "external_id": [ + "T1087" + ], + "refs": [ + "https:\/\/attack.mitre.org\/techniques\/T1087", + "http:\/\/technet.microsoft.com\/en-us\/magazine\/2008.06.srp.aspx", + "http:\/\/www.sans.org\/reading-room\/whitepapers\/application\/application-whitelisting-panacea-propaganda-33599", + "http:\/\/blog.jpcert.or.jp\/2016\/01\/windows-commands-abused-by-attackers.html", + "https:\/\/www.iad.gov\/iad\/library\/ia-guidance\/tech-briefs\/application-whitelisting-using-microsoft-applocker.cfm", + "https:\/\/technet.microsoft.com\/en-us\/library\/ee791851.aspx", + "https:\/\/www.stigviewer.com\/stig\/microsoft_windows_server_2012_member_server\/2013-07-25\/finding\/WN12-CC-000077" + ] + } + } + ] + } + ], + "ShadowAttribute": [], + "Tag": [ + { + "id": "717", + "name": "misp-galaxy:guidelines=\"DoS or overload of government websites\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "718", + "name": "misp-galaxy:mitre-course-of-action=\"Account Discovery Mitigation - T1087\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + } + ] + } + ] + }, + { + "id": "166", + "name": "virustotal-report", + "meta-category": "misc", + "description": "VirusTotal report", + "template_uuid": "d7dd0154-e04f-4c34-a2fb-79f3a3a52aa4", + "template_version": "1", + "event_id": "30", + "uuid": "eefb6d88-9cc1-4d65-b266-b2e82a2464b9", + "timestamp": "1523391190", + "distribution": "5", + "sharing_group_id": "0", + "comment": "", + "deleted": false, + "ObjectReference": [], + "Attribute": [ + { + "id": "5298", + "type": "link", + "category": "External analysis", + "to_ids": false, + "uuid": "5acd1ad6-61c4-45e4-98f6-4bb802de0b81", + "event_id": "30", + "distribution": "5", + "timestamp": "1523391190", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "166", + "object_relation": "permalink", + "value": "https:\/\/www.virustotal.com\/file\/3208efe96d14f5a6a2840daecbead6b0f4d73c5a05192a1a8eef8b50bbfb4bc1\/analysis\/1523371298\/", + "Galaxy": [], + "ShadowAttribute": [] + }, + { + "id": "5299", + "type": "text", + "category": "Other", + "to_ids": false, + "uuid": "5acd1ad7-b308-4547-96b5-41f902de0b81", + "event_id": "30", + "distribution": "5", + "timestamp": "1523391191", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "166", + "object_relation": "detection-ratio", + "value": "44\/66", + "Galaxy": [ + { + "id": "12", + "uuid": "d752161c-78f6-11e7-a0ea-bfa79b407ce4", + "name": "Malware", + "type": "mitre-malware", + "description": "Name of ATT&CK software", + "version": "6", + "icon": "optin-monster", + "namespace": "mitre-attack", + "GalaxyCluster": [ + { + "id": "2306", + "collection_uuid": "fb575479-14ef-41e9-bfab-0b7cf10bec73", + "type": "mitre-malware", + "value": "ADVSTORESHELL - S0045", + "tag_name": "misp-galaxy:mitre-malware=\"ADVSTORESHELL - S0045\"", + "description": "[ADVSTORESHELL](https:\/\/attack.mitre.org\/software\/S0045) is a spying backdoor that has been used by [APT28](https:\/\/attack.mitre.org\/groups\/G0007) from at least 2012 to 2016. It is generally used for long-term espionage and is deployed on targets deemed interesting after a reconnaissance phase. (Citation: Kaspersky Sofacy) (Citation: ESET Sednit Part 2)", + "galaxy_id": "12", + "source": "https:\/\/github.com\/mitre\/cti", + "authors": [ + "MITRE" + ], + "version": "11", + "uuid": "", + "tag_id": "719", + "meta": { + "external_id": [ + "S0045" + ], + "mitre_platforms": [ + "Windows" + ], + "refs": [ + "https:\/\/attack.mitre.org\/software\/S0045", + "https:\/\/securelist.com\/sofacy-apt-hits-high-profile-targets-with-updated-toolset\/72924\/", + "http:\/\/www.welivesecurity.com\/wp-content\/uploads\/2016\/10\/eset-sednit-part-2.pdf" + ], + "synonyms": [ + "ADVSTORESHELL", + "AZZY", + "EVILTOSS", + "NETUI", + "Sedreco" + ] + } + } + ] + } + ], + "ShadowAttribute": [], + "Tag": [ + { + "id": "719", + "name": "misp-galaxy:mitre-malware=\"ADVSTORESHELL - S0045\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + } + ] + }, + { + "id": "5300", + "type": "datetime", + "category": "Other", + "to_ids": false, + "uuid": "5acd1ad7-c180-4b13-bb89-45ba02de0b81", + "event_id": "30", + "distribution": "5", + "timestamp": "1523391191", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "166", + "object_relation": "last-submission", + "value": "2018-04-10 14:41:38", + "Galaxy": [ + { + "id": "22", + "uuid": "c4e851fa-775f-11e7-8163-b774922098cd", + "name": "Attack Pattern", + "type": "mitre-attack-pattern", + "description": "ATT&CK Tactic", + "version": "7", + "icon": "map", + "namespace": "mitre-attack", + "GalaxyCluster": [ + { + "id": "2925", + "collection_uuid": "15dbf668-795c-41e6-8219-f0447c0e64ce", + "type": "mitre-attack-pattern", + "value": "Permission Groups Discovery - T1069", + "tag_name": "misp-galaxy:mitre-attack-pattern=\"Permission Groups Discovery - T1069\"", + "description": "Adversaries may attempt to find local system or domain-level groups and permissions settings. \n\n### Windows\n\nExamples of commands that can list groups are net group \/domain<\/code> and net localgroup<\/code> using the [Net](https:\/\/attack.mitre.org\/software\/S0039) utility.\n\n### Mac\n\nOn Mac, this same thing can be accomplished with the dscacheutil -q group<\/code> for the domain, or dscl . -list \/Groups<\/code> for local groups.\n\n### Linux\n\nOn Linux, local groups can be enumerated with the groups<\/code> command and domain groups via the ldapsearch<\/code> command.", + "galaxy_id": "22", + "source": "https:\/\/github.com\/mitre\/cti", + "authors": [ + "MITRE" + ], + "version": "8", + "uuid": "", + "tag_id": "720", + "meta": { + "external_id": [ + "CAPEC-576" + ], + "kill_chain": [ + "mitre-attack:discovery" + ], + "mitre_data_sources": [ + "API monitoring", + "Process monitoring", + "Process command-line parameters" + ], + "mitre_platforms": [ + "Linux", + "macOS", + "Windows" + ], + "refs": [ + "https:\/\/attack.mitre.org\/techniques\/T1069", + "https:\/\/capec.mitre.org\/data\/definitions\/576.html" + ] + } + } + ] + }, + { + "id": "24", + "uuid": "f2ef4033-9001-4427-a418-df8c48e6d054", + "name": "Stealer", + "type": "stealer", + "description": "Malware stealer galaxy.", + "version": "1", + "icon": "key", + "namespace": "misp", + "GalaxyCluster": [ + { + "id": "3230", + "collection_uuid": "a6780288-24eb-4006-9ddd-062870c6feec", + "type": "stealer", + "value": "TeleGrab", + "tag_name": "misp-galaxy:stealer=\"TeleGrab\"", + "description": "The first version stole browser credentials and cookies, along with all text files it can find on the system. The second variant added the ability to collect Telegram's desktop cache and key files, as well as login information for the video game storefront Steam.", + "galaxy_id": "24", + "source": "Open Sources", + "authors": [ + "raw-data" + ], + "version": "4", + "uuid": "", + "tag_id": "721", + "meta": { + "date": [ + "March 2018." + ], + "refs": [ + "https:\/\/blog.talosintelligence.com\/2018\/05\/telegrab.html" + ] + } + } + ] + }, + { + "id": "27", + "uuid": "84310ba3-fa6a-44aa-b378-b9e3271c58fa", + "name": "Android", + "type": "android", + "description": "Android malware galaxy based on multiple open sources.", + "version": "3", + "icon": "android", + "namespace": "misp", + "GalaxyCluster": [ + { + "id": "3539", + "collection_uuid": "ce1a9641-5bb8-4a61-990a-870e9ef36ac1", + "type": "android", + "value": "Adwind", + "tag_name": "misp-galaxy:android=\"Adwind\"", + "description": "Adwind is a backdoor written purely in Java that targets system supporting the Java runtime environment. Commands that can be used, among other things, to display messages on the system, open URLs, update the malware, download\/execute files, and download\/load plugins. According to the author, the backdoor component can run on Windows, Mac OS, Linux and Android platforms providing rich capabilities for remote control, data gathering, data exfiltration and lateral movement.", + "galaxy_id": "27", + "source": "Open Sources", + "authors": [ + "Unknown" + ], + "version": "18", + "uuid": "", + "tag_id": "722", + "meta": { + "refs": [ + "https:\/\/securelist.com\/adwind-faq\/73660\/" + ], + "synonyms": [ + "AlienSpy", + "Frutas", + "Unrecom", + "Sockrat", + "Jsocket", + "jRat", + "Backdoor:Java\/Adwind" + ] + } + } + ] + } + ], + "ShadowAttribute": [], + "Tag": [ + { + "id": "720", + "name": "misp-galaxy:mitre-attack-pattern=\"Permission Groups Discovery - T1069\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "721", + "name": "misp-galaxy:stealer=\"TeleGrab\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "722", + "name": "misp-galaxy:android=\"Adwind\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + } + ] + } + ] + } + ], + "Tag": [ + { + "id": "4", + "name": "tlp:white", + "colour": "#ffffff", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "12", + "name": "malware_classification:malware-category=\"Ransomware\"", + "colour": "#2c4f00", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "57", + "name": "circl:incident-classification=\"malware\"", + "colour": "#3c7700", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "68", + "name": "ms-caro-malware-full:malware-type=\"Joke\"", + "colour": "#001637", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "19", + "name": "workflow:todo=\"create-missing-misp-galaxy-cluster-values\"", + "colour": "#850048", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "723", + "name": "misp-galaxy:guidelines=\"Defacement, DoS or overload of websites or other systems used for publication of the results\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "724", + "name": "misp-galaxy:guidelines=\"Leak of confidential information\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "725", + "name": "misp-galaxy:guidelines=\"Tampering or DoS of communication links uesd to transfer (interim) results\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "726", + "name": "misp-galaxy:mitre-intrusion-set=\"APT1 - G0006\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "727", + "name": "misp-galaxy:mitre-intrusion-set=\"APT12 - G0005\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "728", + "name": "misp-galaxy:mitre-malware=\"ANDROIDOS_ANSERVER.A - S0310\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "729", + "name": "misp-galaxy:mitre-malware=\"BACKSPACE - S0031\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + } + ] + } +} \ No newline at end of file diff --git a/tests/test_reportlab.py b/tests/test_reportlab.py index 721cb7f..3ccb497 100644 --- a/tests/test_reportlab.py +++ b/tests/test_reportlab.py @@ -1,33 +1,34 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import os +import sys +import time import unittest -from pymisp import MISPEvent +from pymisp import MISPEvent from pymisp.tools import reportlab_generator -import sys -import os -import time +manual_testing = True -manual_testing = False class TestMISPEvent(unittest.TestCase): def setUp(self): self.maxDiff = None self.mispevent = MISPEvent() - if not manual_testing : + if not manual_testing: self.root = "tests/" - else : + else: self.root = "" self.test_folder = self.root + "reportlab_testfiles/" self.test_batch_folder = self.root + "OSINT_output/" + self.storage_folder_OSINT = self.root + "OSINT_PDF/" self.test_image_folder = self.root + "image_json/" 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"] - + self.moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description", + "Activate_galaxy_description"] def init_event(self): self.mispevent.info = 'This is a test' @@ -94,12 +95,13 @@ class TestMISPEvent(unittest.TestCase): config = {} config[self.moduleconfig[0]] = "http://localhost:8080" - config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[1]] = "My Wonderful CERT" self.init_event() self.mispevent.load_file(self.test_folder + 'very_long_event.json') - reportlab_generator.register_value_to_file(reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), - self.storage_folder + "config_complete_event.pdf") + reportlab_generator.register_value_to_file( + reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "config_complete_event.pdf") def test_partial_0_config_json(self): if self.check_python_2(): @@ -111,8 +113,9 @@ class TestMISPEvent(unittest.TestCase): self.init_event() self.mispevent.load_file(self.test_folder + 'very_long_event.json') - reportlab_generator.register_value_to_file(reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), - self.storage_folder + "config_partial_0_event.pdf") + reportlab_generator.register_value_to_file( + reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "config_partial_0_event.pdf") def test_partial_1_config_json(self): if self.check_python_2(): @@ -120,12 +123,13 @@ class TestMISPEvent(unittest.TestCase): else: config = {} - config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[1]] = "My Wonderful CERT" self.init_event() self.mispevent.load_file(self.test_folder + 'very_long_event.json') - reportlab_generator.register_value_to_file(reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), - self.storage_folder + "config_partial_1_event.pdf") + reportlab_generator.register_value_to_file( + reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "config_partial_1_event.pdf") def test_image_json(self): if self.check_python_2(): @@ -134,12 +138,13 @@ class TestMISPEvent(unittest.TestCase): config = {} config[self.moduleconfig[0]] = "http://localhost:8080" - config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[1]] = "My Wonderful CERT" self.init_event() self.mispevent.load_file(self.test_folder + 'image_event.json') - reportlab_generator.register_value_to_file(reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), - self.storage_folder + "image_event.pdf") + reportlab_generator.register_value_to_file( + reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "image_event.pdf") def test_objects_1_json(self): if self.check_python_2(): @@ -148,12 +153,13 @@ class TestMISPEvent(unittest.TestCase): config = {} config[self.moduleconfig[0]] = "http://localhost:8080" - config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[1]] = "My Wonderful CERT" self.init_event() self.mispevent.load_file(self.test_folder + 'mainly_objects_1.json') - reportlab_generator.register_value_to_file(reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), - self.storage_folder + "mainly_objects_1.pdf") + reportlab_generator.register_value_to_file( + reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "mainly_objects_1.pdf") def test_objects_2_json(self): if self.check_python_2(): @@ -162,12 +168,13 @@ class TestMISPEvent(unittest.TestCase): config = {} config[self.moduleconfig[0]] = "http://localhost:8080" - config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[1]] = "My Wonderful CERT" self.init_event() self.mispevent.load_file(self.test_folder + 'mainly_objects_2.json') - reportlab_generator.register_value_to_file(reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), - self.storage_folder + "mainly_objects_2.pdf") + reportlab_generator.register_value_to_file( + reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "mainly_objects_2.pdf") def test_sightings_1_json(self): if self.check_python_2(): @@ -176,12 +183,13 @@ class TestMISPEvent(unittest.TestCase): config = {} config[self.moduleconfig[0]] = "http://localhost:8080" - config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[1]] = "My Wonderful CERT" self.init_event() self.mispevent.load_file(self.test_folder + 'sighting_1.json') - reportlab_generator.register_value_to_file(reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), - self.storage_folder + "sighting_1.pdf") + reportlab_generator.register_value_to_file( + reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "sighting_1.pdf") def test_sightings_2_json(self): if self.check_python_2(): @@ -190,12 +198,13 @@ class TestMISPEvent(unittest.TestCase): config = {} config[self.moduleconfig[0]] = "http://localhost:8080" - config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[1]] = "My Wonderful CERT" self.init_event() self.mispevent.load_file(self.test_folder + 'sighting_2.json') - reportlab_generator.register_value_to_file(reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), - self.storage_folder + "sighting_2.pdf") + reportlab_generator.register_value_to_file( + reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "sighting_2.pdf") def test_textual_json(self): if self.check_python_2(): @@ -204,21 +213,38 @@ class TestMISPEvent(unittest.TestCase): config = {} config[self.moduleconfig[0]] = "http://localhost:8080" - config[self.moduleconfig[1]] = "My Wonderful CERT" - config[self.moduleconfig[2]] = True + config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[2]] = True self.init_event() self.mispevent.load_file(self.test_folder + 'very_long_event.json') - reportlab_generator.register_value_to_file(reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), - self.storage_folder + "textual.pdf") + reportlab_generator.register_value_to_file( + reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "textual.pdf") + def test_galaxy_1(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 + + 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 + "galaxy_1.pdf") def test_batch_image_events(self): # Test case ONLY for manual testing. Needs to download a full list of image events ! if self.check_python_2(): self.assertTrue(True) - elif not manual_testing : + elif not manual_testing: self.assertTrue(True) else: self.init_event() @@ -246,7 +272,7 @@ class TestMISPEvent(unittest.TestCase): if self.check_python_2(): self.assertTrue(True) - elif not manual_testing : + elif not manual_testing: self.assertTrue(True) else: self.init_event() @@ -265,7 +291,7 @@ class TestMISPEvent(unittest.TestCase): reportlab_generator.register_value_to_file( reportlab_generator.convert_event_in_pdf_buffer(self.mispevent), - self.storage_folder + curr_file + ".pdf") + self.storage_folder_OSINT + curr_file + ".pdf") print("Elapsed time : " + str(time.time() - t)) # Local run : 1958.930s for 1064 files @@ -274,15 +300,15 @@ class TestMISPEvent(unittest.TestCase): if self.check_python_2(): self.assertTrue(True) - elif not manual_testing : + elif not manual_testing: self.assertTrue(True) else: self.init_event() config = {} config[self.moduleconfig[0]] = "http://localhost:8080" - config[self.moduleconfig[1]] = "My Wonderful CERT" - config[self.moduleconfig[2]] = True + config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[2]] = True file_nb = str(len(os.listdir(self.test_batch_folder))) i = 0 @@ -298,6 +324,6 @@ class TestMISPEvent(unittest.TestCase): reportlab_generator.register_value_to_file( reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), - self.storage_folder + curr_file + ".pdf") + self.storage_folder_OSINT + curr_file + ".pdf") print("Elapsed time : " + str(time.time() - t)) - # Local run : 1958.930s for 1064 files + # Local run : 1513.283s for 1064 files From 6031a7d426bca4811f71aaacaf00d062da612b3a Mon Sep 17 00:00:00 2001 From: Falconieri Date: Thu, 28 Feb 2019 13:44:54 +0100 Subject: [PATCH 02/10] chg: [exportpdf] BIG refactoring. Classes, comments, Galaxy starting --- pymisp/tools/reportlab_generator.py | 1892 ++++++++++++++------------- 1 file changed, 1001 insertions(+), 891 deletions(-) diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index 9cf748d..eb5f6a7 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -6,12 +6,8 @@ import base64 import logging import pprint from io import BytesIO -from functools import partial - import sys -from orca.cmdnames import TABLE_CELL_FIRST - if sys.version_info.major >= 3: from html import escape # import PIL @@ -223,7 +219,44 @@ def get_sample_styles(): sample_style_sheet.list() -# "INTERNAL" METHODS. Not meant to be used outside of this class. +######################################################################## +# General Event's Attributes formater tools + +def uuid_to_url(baseurl, uuid): + ''' + Return an url constructed from the MISP baseurl and the uuid of the event, to go to this event on this MISP + :param baseurl: the baseurl of the MISP instnce e.g. http://localhost:8080 or http://localhost:8080/ + :param uuid: the uuid of the event that we want to have a link to + :return: the complete URL to go to this event on this MISP instance + ''' + if baseurl[len(baseurl) - 1] != "/": + baseurl += "/" + return baseurl + "events/view/" + uuid + + +def create_flowable_table_from_data(data, col_w=COL_WIDTHS): + ''' + 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) + :return: a Table - with styles - to add to the pdf + ''' + # Create the table + curr_table = Table(data, col_w) + + # Aside notes : + # colWidths='*' does a 100% and share the space automatically + # 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) + lines_style = lines_style_generator(data) + general_style = general_style_generator() + + # Make the table nicer + curr_table.setStyle(TableStyle(general_style + alternate_colors_style + lines_style)) + + return curr_table + def alternate_colors_style_generator(data): ''' @@ -318,6 +351,7 @@ def get_table_styles(): def safe_string(bad_str): return escape(str(bad_str)) + def is_safe_attribute(curr_object, attribute_name): return hasattr(curr_object, attribute_name) and getattr(curr_object, attribute_name) is not None and getattr( curr_object, attribute_name) != "" @@ -332,946 +366,1011 @@ def is_safe_attribute_table(curr_object, attribute_name): return hasattr(curr_object, attribute_name) and getattr(curr_object, attribute_name) is not None and getattr( curr_object, attribute_name) != [] +def is_in_config(config, index): + return config is not None and moduleconfig[index] in config + ######################################################################## -# General attribut formater +# Functions grouped by misp object type -def get_unoverflowable_paragraph(dirty_string, curr_style, do_escape_string=True): + +class Value_Formatter(): ''' - Create a paragraph that can fit on a cell of one page. Mostly hardcoded values. - This method can be improved (get the exact size of the current frame, and limit the paragraph to this size.) - This might be worst look at KeepInFrame (which hasn't went well so far) - :param do_escape_string: Activate the escaping (may be useful to add inline HTML, e.g. hyperlinks) - :param dirty_string: String to transform - :param curr_style: Style to apply to the returned paragraph - :return: + "item" parameter should be as follow, a list of name, in order : + ["Name to be print in the pdf", "json property access name", + " Name to be display if no values found in the misp_event"] ''' - if do_escape_string: - sanitized_str = str(escape(str(dirty_string))) - else: - sanitized_str = dirty_string + # ---------------------------------------------------------------------- + def __init__(self, config, col1_style, col2_style): + self.config = config + self.col1_style = col1_style + self.col2_style = 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) + # ---------------------------------------------------------------------- + ######################################################################## + # General attribut formater + def get_col1_paragraph(self, dirty_string): + return self.get_unoverflowable_paragraph(dirty_string, self.col1_style) - # If there is enough space, directly send back the sanitized paragraph - if w <= FRAME_MAX_WIDTH and h <= FRAME_MAX_HEIGHT: - answer_paragraph = Paragraph(sanitized_str, curr_style) - else: - # Otherwise, cut the content to fit the paragraph (Dichotomy) - max_carac_amount = int((FRAME_MAX_HEIGHT / (h * 1.0)) * len(sanitized_str)) + def get_unoverflowable_paragraph(self, dirty_string, curr_style = None, do_escape_string=True): + ''' + Create a paragraph that can fit on a cell displayed one page maximum. + This method can be improved (get the exact size of the current frame, and limit the paragraph to this size.) + KeepInFrame may give a nicer solution (not for me so far) + :param do_escape_string: Activate the escaping (may be useful to add inline HTML, e.g. hyperlinks) + :param dirty_string: String to transform + :param curr_style: Style to apply to the returned paragraph + :return: + ''' + if do_escape_string: + sanitized_str = safe_string(dirty_string) + else: + sanitized_str = dirty_string - i = 0 - MAX_ITERATION = 10 - limited_string = "" - while (w > FRAME_MAX_WIDTH or h > FRAME_MAX_HEIGHT) and i < MAX_ITERATION: - i += 1 - limited_string = sanitized_str[:max_carac_amount] # .replace("\n", "").replace("\r", "") - w, h = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style).wrap(FRAME_MAX_WIDTH, FRAME_MAX_HEIGHT) - max_carac_amount = int(max_carac_amount / 2) + if curr_style is None: + 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) + + # If there is enough space, directly send back the sanitized paragraph if w <= FRAME_MAX_WIDTH and h <= FRAME_MAX_HEIGHT: - answer_paragraph = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style) + answer_paragraph = Paragraph(sanitized_str, curr_style) else: - # We may still end with a not short enough string - answer_paragraph = Paragraph(STR_TOO_LONG_WARNING, curr_style) + # Otherwise, cut the content to fit the paragraph (Dichotomy) + max_carac_amount = int((FRAME_MAX_HEIGHT / (h * 1.0)) * len(sanitized_str)) - return answer_paragraph + i = 0 + MAX_ITERATION = 10 + limited_string = "" + while (w > FRAME_MAX_WIDTH or h > FRAME_MAX_HEIGHT) and i < MAX_ITERATION: + i += 1 + limited_string = sanitized_str[:max_carac_amount] # .replace("\n", "").replace("\r", "") + w, h = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style).wrap(FRAME_MAX_WIDTH, FRAME_MAX_HEIGHT) + max_carac_amount = int(max_carac_amount / 2) -######################################################################## -# Specific attribute formater - -def get_value_link_to_event(misp_event, item, col2_style, config=None, color=True): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event uuid, with or without link - :param config: Config dictionnary provided by MISP instance, via misp-modules (with baseurl) - :param misp_event: A misp event with or without "uuid" attributes - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "uuid" - ''' - - # Does MispEven has the attribute ? - if is_safe_attribute(misp_event, item[1]): - # It has the requested attribute .. building upon it. - - # Does misp_object has an uuid and do we know the baseurl ? - if is_safe_attribute(misp_event, "uuid") and config is not None and moduleconfig[0] in config: - # We can build links - curr_uuid = str(getattr(misp_event, "uuid")) - curr_baseurl = config[moduleconfig[0]] - curr_url = uuid_to_url(curr_baseurl, curr_uuid) - html_url = "" + safe_string(getattr(misp_event, item[1])) + "" - - if color: - # They want fancy colors - html_url = "" + html_url + "" - - # Construct final paragraph - answer = get_unoverflowable_paragraph(html_url, col2_style, False) - - else: - # We can't build links - answer = get_unoverflowable_paragraph(getattr(misp_event, item[1]), col2_style) - - else: - # No it doesn't, so we directly give the default answer - answer = Paragraph(item[2], col2_style) - - return answer - - -def get_date_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event date - :param misp_event: A misp event with or without "date" attributes - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "date" - ''' - if is_safe_attribute(misp_event, item[1]): - return Paragraph(safe_string(getattr(misp_event, item[1])), col2_style) - return Paragraph(item[2], col2_style) - - -def get_owner_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event owner - :param misp_event: A misp event with or without "owner" attributes - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "owner" - ''' - if is_safe_attribute(misp_event, item[1]): - return Paragraph(safe_string(getattr(misp_event, item[1])), col2_style) - return Paragraph(item[2], col2_style) - - -def get_threat_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event threat - :param misp_event: A misp event with or without "threat" attributes - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "threat" - ''' - if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in threat_map: - return Paragraph(threat_map[safe_string(getattr(misp_event, item[1]))], col2_style) - return Paragraph(item[2], col2_style) - - -def get_analysis_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event analysis - :param misp_event: A misp event with or without "analysis" attributes - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "analysis" - ''' - if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in analysis_map: - return Paragraph(analysis_map[safe_string(getattr(misp_event, item[1]))], col2_style) - return Paragraph(item[2], col2_style) - - -def get_timestamp_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event timestamp - :param misp_event: A misp event with or without "timestamp" attributes - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "timestamp" - ''' - if is_safe_attribute(misp_event, item[1]): - return Paragraph(safe_string(getattr(misp_event, item[1]).strftime(EXPORT_DATE_FORMAT)), col2_style) - return Paragraph(item[2], col2_style) - - -def get_creator_organisation_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event creator organisation - :param misp_event: A misp event with or without "timestamp" attributes - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event", "json property access name (second level)"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "creator organisation" - ''' - if is_safe_attribute(misp_event, item[1]): - return Paragraph(safe_string(getattr(getattr(misp_event, item[1]), item[3])), col2_style) - return Paragraph(item[2], col2_style) - - -def get_attributes_number_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event attributes - :param misp_event: A misp event with or without "attributes" attributes - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "attributes" - ''' - if is_safe_attribute(misp_event, item[1]): - return Paragraph(safe_string(len(getattr(misp_event, item[1]))), col2_style) - return Paragraph(item[2], col2_style) - - -def get_tag_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event tags - :param misp_event: A misp event with or without "tags" attributes - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "tags" - ''' - if is_safe_attribute_table(misp_event, item[1]): - table_event_tags = create_flowable_table_from_tags(misp_event) - return table_event_tags - return Paragraph(item[2], col2_style) - - -def get_galaxy_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event galaxies - :param misp_event: A misp event with or without "galaxies" attributes - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "galaxies" - ''' - if is_safe_attribute_table(misp_event, item[1]): - table_event_tags = create_flowable_table_from_galaxies(misp_event) - return table_event_tags - return Paragraph(item[2], col2_style) - - -def get_published_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event published/published_time - More information on how to play with paragraph into reportlab cells : - https://stackoverflow.com/questions/11810008/reportlab-add-two-paragraphs-into-one-table-cell - :param misp_event: A misp event with or without "published"/"publish_timestamp" attributes - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event", json property access name (for timestamp")] - e.g. item = ["Published", 'published', "None", "publish_timestamp"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "published"/"publish_timestamp" - ''' - - RED_COLOR = '#ff0000' - GREEN_COLOR = '#008000' - YES_ANSWER = " Yes (" - NO_ANSWER = "No" - - # Formatting similar to MISP Event web view - if is_safe_attribute(misp_event, item[1]): - if getattr(misp_event, item[1]): # == True - if is_safe_attribute(misp_event, item[3]): - # Published and have published date - answer = Paragraph(YES_ANSWER + getattr(misp_event, item[3]).strftime(EXPORT_DATE_FORMAT) + ")", - col2_style) + if w <= FRAME_MAX_WIDTH and h <= FRAME_MAX_HEIGHT: + answer_paragraph = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style) else: - # Published without published date - answer = Paragraph(YES_ANSWER + "no date)", col2_style) + # We may still end with a not short enough string + answer_paragraph = Paragraph(STR_TOO_LONG_WARNING, curr_style) + + return answer_paragraph + + + def get_value_link_to_event(self, misp_event, item, curr_style = None, color=True): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event uuid, with or without link + :param color: Boolean to give a color or not to the generate link (good link color) + :param config: Config dictionnary provided by MISP instance, via misp-modules (with baseurl) + :param misp_event: A misp event with or without "uuid" 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 "uuid" + ''' + + if curr_style is None: + curr_style = self.col2_style + + # Does MispEven has the attribute ? + if is_safe_attribute(misp_event, item[1]): + # It has the requested attribute .. building upon it. + + # Does misp_object has an uuid and do we know the baseurl ? + if is_safe_attribute(misp_event, "uuid") and is_in_config(self.config,0): + # We can build links + curr_uuid = str(getattr(misp_event, "uuid")) + curr_baseurl = self.config[moduleconfig[0]] + curr_url = uuid_to_url(curr_baseurl, curr_uuid) + html_url = "" + safe_string(getattr(misp_event, item[1])) + "" + + if color: + # They want fancy colors + html_url = "" + html_url + "" + + # Construct final paragraph + answer = self.get_unoverflowable_paragraph(html_url, curr_style=curr_style, do_escape_string=False) + + else: + # We can't build links + answer = self.get_unoverflowable_paragraph(getattr(misp_event, item[1]), curr_style=curr_style) else: - # Not published - answer = Paragraph(NO_ANSWER, col2_style) - else: - # Does not have a published attribute - answer = Paragraph(item[2], col2_style) + # No it doesn't, so we directly give the default answer + answer = self.get_unoverflowable_paragraph(item[2], curr_style=curr_style) - return answer + return answer -def create_flowable_table_from_one_attribute(misp_attribute, config=None): - ''' - Returns a table (flowalbe) representing the attribute - :param misp_attribute: A misp attribute - :return: a table representing this misp's attribute's attributes, to add to the pdf as a flowable - ''' + ######################################################################## + # Specific attribute formater - data = [] - col1_style, col2_style = get_table_styles() + def get_date_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event date + :param misp_event: A misp event with or without "date" 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 "date" + ''' + if is_safe_attribute(misp_event, item[1]): + return self.get_unoverflowable_paragraph(safe_string(getattr(misp_event, item[1]))) + return self.get_unoverflowable_paragraph(item[2]) - # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, - # to_display_if_not_present) are store in the following list - list_attr_automated = [["UUID", 'uuid', "None"], - ["Category", 'category', "None"], - ["Comment", 'comment', "None"], - ["Type", 'type', "None"], - ["Value", 'value', "None"]] - # Handle the special case of links - STANDARD_TYPE = True - if is_safe_attribute(misp_attribute, 'type') and (getattr(misp_attribute, 'type') in [LINK_TYPE, URL_TYPE]): - # getattr(misp_attribute, 'type') == LINK_TYPE or getattr(misp_attribute, 'type') == URL_TYPE): - # Special case for links - STANDARD_TYPE = False + def get_owner_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event owner + :param misp_event: A misp event with or without "owner" 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 "owner" + ''' + if is_safe_attribute(misp_event, item[1]): + return self.get_unoverflowable_paragraph(safe_string(getattr(misp_event, item[1]))) + return self.get_unoverflowable_paragraph(item[2]) - # Automated adding of standard (python) attributes of the misp event - for item in list_attr_automated: - if is_safe_attribute(misp_attribute, item[1]) and (STANDARD_TYPE or item[1] != 'value'): - # The attribute exists, we fetch it and create the row - data.append([Paragraph(item[0], col1_style), - get_unoverflowable_paragraph(getattr(misp_attribute, item[1]), col2_style)]) - # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : - # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) + def get_threat_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event threat + :param misp_event: A misp event with or without "threat" 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 "threat" + ''' + if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in threat_map: + return self.get_unoverflowable_paragraph(threat_map[safe_string(getattr(misp_event, item[1]))], do_escape_string=False) + return self.get_unoverflowable_paragraph(item[2]) - # Handle Special case for links (Value) - There were not written in the previous loop - item = ["Value", 'value', "None"] - if not STANDARD_TYPE and is_safe_attribute(misp_attribute, item[1]): - data.append([Paragraph(item[0], col1_style), get_good_or_bad_link(misp_attribute, item, col2_style)]) - # Handle pictures - item = ["Data", 'data', "None"] - if is_safe_attribute(misp_attribute, item[1]) and getattr(misp_attribute, 'type') == IMAGE_TYPE: - data.append([Paragraph(item[0], col1_style), get_image_value(misp_attribute, item, col2_style)]) + def get_analysis_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event analysis + :param misp_event: A misp event with or without "analysis" 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 "analysis" + ''' + if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in analysis_map: + return self.get_unoverflowable_paragraph(analysis_map[safe_string(getattr(misp_event, item[1]))], do_escape_string=False) + return self.get_unoverflowable_paragraph(item[2]) - # Tags - item = ["Tags", 'Tag', "None"] - if is_safe_attribute_table(misp_attribute, item[1]): - data.append([Paragraph(item[0], col1_style), get_tag_value(misp_attribute, item, col2_style)]) - # Sighting - item = ["Sighting", 'Sighting', "None"] - if is_safe_attribute_table(misp_attribute, item[1]): - data.append([Paragraph(item[0], col1_style), - create_flowable_paragraph_from_sightings(misp_attribute, item, col2_style)]) + def get_timestamp_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event timestamp + :param misp_event: A misp event with or without "timestamp" 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 "timestamp" + ''' + if is_safe_attribute(misp_event, item[1]): + return self.get_unoverflowable_paragraph(safe_string(getattr(misp_event, item[1]).strftime(EXPORT_DATE_FORMAT))) + return self.get_unoverflowable_paragraph(item[2]) - if config is not None and moduleconfig[3] in config: - # Galaxies - item = ["Galaxies", 'Galaxy', "None"] + + def get_creator_organisation_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event creator organisation + :param misp_event: A misp event with or without "timestamp" attributes + :param item: a list of name, in order : + ["Name to be print in the pdf", "json property access name", + " Name to be display if no values found in the misp_event", "json property access name (second level)"] + :param col2_style: style to be applied on the returned paragraph + :return: a Paragraph to add in the pdf, regarding the values of "creator organisation" + ''' + if is_safe_attribute(misp_event, item[1]): + return self.get_unoverflowable_paragraph(safe_string(getattr(getattr(misp_event, item[1]), item[3]))) + return self.get_unoverflowable_paragraph(item[2]) + + + def get_attributes_number_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event attributes + :param misp_event: A misp event with or without "attributes" 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 "attributes" + ''' + if is_safe_attribute(misp_event, item[1]): + return self.get_unoverflowable_paragraph(safe_string(len(getattr(misp_event, item[1])))) + return self.get_unoverflowable_paragraph(item[2]) + + + + def get_published_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event published/published_time + More information on how to play with paragraph into reportlab cells : + https://stackoverflow.com/questions/11810008/reportlab-add-two-paragraphs-into-one-table-cell + :param misp_event: A misp event with or without "published"/"publish_timestamp" attributes + :param item: a list of name, in order : + ["Name to be print in the pdf", "json property access name", + " Name to be display if no values found in the misp_event", json property access name (for timestamp")] + e.g. item = ["Published", 'published', "None", "publish_timestamp"] + :param col2_style: style to be applied on the returned paragraph + :return: a Paragraph to add in the pdf, regarding the values of "published"/"publish_timestamp" + ''' + + RED_COLOR = '#ff0000' + GREEN_COLOR = '#008000' + YES_ANSWER = " Yes (" + NO_ANSWER = "No" + + # Formatting similar to MISP Event web view + if is_safe_attribute(misp_event, item[1]): + if getattr(misp_event, item[1]): # == True + if is_safe_attribute(misp_event, item[3]): + # Published and have published date + answer = self.get_unoverflowable_paragraph(YES_ANSWER + getattr(misp_event, item[3]).strftime(EXPORT_DATE_FORMAT) + ")", do_escape_string=False) + else: + # Published without published date + answer = self.get_unoverflowable_paragraph(YES_ANSWER + "no date)", do_escape_string=False) + + else: + # Not published + answer = self.get_unoverflowable_paragraph(NO_ANSWER, do_escape_string=False) + else: + # Does not have a published attribute + answer = self.get_unoverflowable_paragraph(item[2], do_escape_string=False) + + return answer + + + def get_image_value(self, misp_attribute, item): + ''' + Returns a flowable image to add to the pdf given the misp attribute type and data + :param misp_attribute: A misp attribute with type="attachement" and data + :param item: as defined in class definition + :param col2_style: style to be applied on the returned paragraph + :return: a flowable image to add in the pdf, regarding the values of "data" + ''' + + try: + # Get the image + buf = getattr(misp_attribute, item[1]) + + # Create image within a bounded box (to allow pdf creation) + img = Image(buf, width=FRAME_PICTURE_MAX_WIDTH, height=FRAME_PICTURE_MAX_HEIGHT, kind='bound') + answer = img + + except OSError: + logger.error( + "Trying to add an attachment during PDF export generation. Attachement joining failed. Attachement may not be an image.") + answer = self.get_unoverflowable_paragraph( + "" + NOT_A_PICTURE_MESSAGE + "", do_escape_string=False) + + return answer + + def get_good_link(self,misp_attribute, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_attribute value, if this is a link + :param misp_attribute: A misp attribute with a link + :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 this "link" attribute + ''' + return self.get_unoverflowable_paragraph( + "" + getattr( + misp_attribute, item[1]) + "", do_escape_string=False) + + + def get_bad_link(self,misp_attribute, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_attribute value, if this is a link + :param misp_attribute: A misp event with an url + :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 this "url" attribute + ''' + return self.get_unoverflowable_paragraph( + "" + getattr(misp_attribute, + item[1]) + "", do_escape_string=False) + + + def get_good_or_bad_link(self,misp_attribute, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_attribute value, if this is a link or an url + :param misp_attribute: A misp attribute with a link or an url + :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 this "link" or "url" attribute + ''' + + answer = self.get_unoverflowable_paragraph("Not an URL") + + # Handle "Good" links + if getattr(misp_attribute, 'type') == LINK_TYPE: + answer = self.get_good_link(misp_attribute, item) + # Handle "bad "links + elif getattr(misp_attribute, 'type') == URL_TYPE: + answer = self.get_bad_link(misp_attribute, item) + + return answer + + +class Event_Metadata(): + + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.curr_val_f = value_formatter + + # ---------------------------------------------------------------------- + + ######################################################################## + # General Event's Attributes formater + + def create_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 = [] + + # Manual addition + # UUID + item = ["UUID", 'uuid', "None"] + data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_value_link_to_event(misp_event, item)]) + + # Date + item = ["Date", 'date', "None"] + data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_date_value(misp_event, item)]) + + # Owner + item = ["Owner org", 'owner', "None"] + data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_owner_value(misp_event, item)]) + + # Threat + item = ["Threat level", 'threat_level_id', "None"] + data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_threat_value(misp_event, item)]) + + # Analysis + item = ["Analysis", 'analysis', "None"] + data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_analysis_value(misp_event, item)]) + + # Info + item = ["Info", 'info', "None"] + data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_value_link_to_event(misp_event, item)]) + + # Timestamp + item = ["Event date", 'timestamp', "None"] + data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_timestamp_value(misp_event, item)]) + + # Published + item = ["Published", 'published', "None", "publish_timestamp"] + data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_published_value(misp_event, item)]) + + # Creator organisation + item = ["Creator Org", 'Orgc', "None", "name"] + data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_creator_organisation_value(misp_event, item)]) + + # Number of Attributes + item = ["# Attributes", 'Attribute', "None"] + data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_attributes_number_value(misp_event, item)]) + + # Tags + item = ["Tags", 'Tag', "None"] + curr_Tags = Tags(self.config, self.curr_val_f) + data.append([self.curr_val_f.get_col1_paragraph(item[0]), curr_Tags.get_tag_value(misp_event, item)]) + + if is_in_config(self.config, 3): + # Galaxies + item = ["Galaxies", 'Galaxy', "None"] + if is_safe_attribute_table(misp_event, item[1]): + curr_Galaxy = Galaxy(self.config, self.curr_val_f) + data.append([self.curr_val_f.get_col1_paragraph(item[0]), curr_Galaxy.get_galaxy_value(misp_event, item)]) + + return create_flowable_table_from_data(data) + + def create_flowable_description_from_event(self, misp_event): + ''' + Returns a Paragraph presenting a MISP event + :param misp_event: A misp event (complete or not) + :return: a paragraph that can be added to a pdf + ''' + + ''' + The event "{EventName}" | that occurred on {EventDate}, | had been shared by {Organisation Name} | on the {Date}. + ''' + + text = "" + + item = ["Info", 'info', "None"] + if is_safe_attribute(misp_event, item[1]): + text += "The event '" + text += safe_string(getattr(misp_event, item[1])) + text += "'" + else: + text += "This event" + + item = ["Event date", 'timestamp', "None"] + if is_safe_attribute(misp_event, item[1]): + text += " that occurred on " + text += safe_string(getattr(misp_event, item[1]).strftime(EXPORT_DATE_FORMAT)) + text += "," + + item = ["Creator Org", 'Orgc', "None", "name"] + text += " had been shared by " + if is_safe_attribute(misp_event, item[1]): + text += safe_string(getattr(getattr(misp_event, item[1]), item[3])) + else: + text += " an unknown organisation" + + item = ["Date", 'date', "None"] + if is_safe_attribute(misp_event, item[1]): + text += " on the " + text += safe_string(getattr(misp_event, item[1])) + else: + text += " on an unknown date" + text += "." + + ''' + The threat level of this event is {ThreatLevel} and the analysis that was made of this event is {AnalysisLevel}. + ''' + + item = ["Threat level", 'threat_level_id', "None"] + text += " The threat level of this event is " + if is_safe_attribute(misp_event, item[1]) and safe_string(getattr(misp_event, item[1])) in threat_map: + text += threat_map[safe_string(getattr(misp_event, item[1]))] + else: + text += " unknown" + + item = ["Analysis", 'analysis', "None"] + text += " and the analysis that was made of this event is " + if is_safe_attribute(misp_event, item[1]) and safe_string(getattr(misp_event, item[1])) in analysis_map: + text += analysis_map[safe_string(getattr(misp_event, item[1]))] + else: + text += " undefined" + text += "." + + ''' + The event is currently {Published} and has associated attributes {Attribute Number}. + ''' + + item = ["Published", 'published', "None", "publish_timestamp"] + text += " The event is currently " + if is_safe_attribute(misp_event, item[1]) and getattr(misp_event, item[1]): + text += " published" + if is_safe_attribute(misp_event, item[3]): + text += " since " + getattr(misp_event, item[3]).strftime(EXPORT_DATE_FORMAT) + else: + text += " private" + + # Number of Attributes + item = ["# Attributes", 'Attribute', "None"] + text += ", has " + if is_safe_attribute_table(misp_event, item[1]): + text += safe_string(len(getattr(misp_event, item[1]))) + else: + text += " 0" + + text += " associated attributes" + + # Number of Objects + item = ["# Objects", 'Object', "None"] + text += " and has " + if is_safe_attribute_table(misp_event, item[1]): + text += safe_string(len(getattr(misp_event, item[1]))) + else: + text += " 0" + + text += " associated objects." + + ''' + For more information on the event, please consult the rest of the document + ''' + text += "
For more information on the event, please consult following information." + + description_style = ParagraphStyle(name='Description', parent=self.curr_val_f.col2_style, alignment=TA_JUSTIFY) + + return Paragraph(text, description_style) + + + +class Attributes(): + + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.value_formatter = value_formatter + self.sample_style_sheet = getSampleStyleSheet() + # ---------------------------------------------------------------------- + + def create_flowable_table_from_attributes(self, misp_event): + ''' + Returns a list of flowables representing the list of attributes of a misp event. + The list is composed alternatively of headers and tables, to add to the pdf + :param misp_event: A misp event + :return: a table of flowables + ''' + flowable_table = [] + i = 0 + + if is_safe_attribute_table(misp_event, "Attribute"): + # There is some attributes for this object + 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(Paragraph("Attribute #" + str(i), self.sample_style_sheet['Heading4'])) + flowable_table.append(self.create_flowable_table_from_one_attribute(item)) + i += 1 + else: + # No attributes for this object + flowable_table.append(Paragraph("No attributes", self.sample_style_sheet['Heading4'])) + + return flowable_table + + def create_flowable_table_from_one_attribute(self, misp_attribute): + ''' + Returns a table (flowalbe) representing the attribute + :param misp_attribute: A misp attribute + :return: a table representing this misp's attribute's attributes, to add to the pdf as a flowable + ''' + + data = [] + col1_style, col2_style = get_table_styles() + + # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, + # to_display_if_not_present) are store in the following list + list_attr_automated = [["UUID", 'uuid', "None"], + ["Category", 'category', "None"], + ["Comment", 'comment', "None"], + ["Type", 'type', "None"], + ["Value", 'value', "None"]] + + # Handle the special case of links + STANDARD_TYPE = True + if is_safe_attribute(misp_attribute, 'type') and (getattr(misp_attribute, 'type') in [LINK_TYPE, URL_TYPE]): + # getattr(misp_attribute, 'type') == LINK_TYPE or getattr(misp_attribute, 'type') == URL_TYPE): + # Special case for links + STANDARD_TYPE = False + + # Automated adding of standard (python) attributes of the misp event + for item in list_attr_automated: + if is_safe_attribute(misp_attribute, item[1]) and (STANDARD_TYPE or item[1] != 'value'): + # The attribute exists, we fetch it and create the row + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_unoverflowable_paragraph(getattr(misp_attribute, item[1]))]) + + # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : + # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) + + # Handle Special case for links (Value) - There were not written in the previous loop + item = ["Value", 'value', "None"] + if not STANDARD_TYPE and is_safe_attribute(misp_attribute, item[1]): + data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_good_or_bad_link(misp_attribute, item)]) + + # Handle pictures + item = ["Data", 'data', "None"] + if is_safe_attribute(misp_attribute, item[1]) and getattr(misp_attribute, 'type') == IMAGE_TYPE: + data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_image_value(misp_attribute, item)]) + + # Tags + item = ["Tags", 'Tag', "None"] + curr_Tags = Tags(self.config, self.value_formatter) if is_safe_attribute_table(misp_attribute, item[1]): - data.append([Paragraph(item[0], col1_style), get_galaxy_value(misp_attribute, item, col2_style)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), curr_Tags.get_tag_value(misp_attribute, item)]) - return create_flowable_table_from_data(data) + # Sighting + item = ["Sighting", 'Sighting', "None"] + curr_Sighting = Sightings(self.config, self.value_formatter) + if is_safe_attribute_table(misp_attribute, item[1]): + data.append([self.value_formatter.get_col1_paragraph(item[0]), + curr_Sighting.create_flowable_paragraph_from_sightings(misp_attribute, item)]) + + if is_in_config(self.config, 3): + # Galaxies + item = ["Galaxies", 'Galaxy', "None"] + curr_Galaxy = Galaxy(self.config, self.value_formatter) + if is_safe_attribute_table(misp_attribute, item[1]): + data.append([Paragraph(item[0], col1_style), curr_Galaxy.get_galaxy_value(misp_attribute, item)]) + + return create_flowable_table_from_data(data) -def create_flowable_table_from_one_object(misp_object, config=None): - ''' - Returns a table (flowable) representing the object - :param misp_attribute: A misp object - :return: a table representing this misp's object's attributes, to add to the pdf as a flowable - ''' - data = [] - col1_style, col2_style = get_table_styles() +class Tags(): - # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, - # to_display_if_not_present) are store in the following list - list_attr_automated = [["UUID", 'uuid', "None"], - ["Description", 'description', "None"], - ["Meta Category", 'meta-category', "None"], - ["Object Name", 'name', "None"], - ["Comment", 'comment', "None"], - ["Type", 'type', "None"]] + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.value_formatter = value_formatter - # Automated adding of standard (python) attributes of the misp object - for item in list_attr_automated: - if is_safe_attribute(misp_object, item[1]): - # The attribute exists, we fetch it and create the row - data.append([Paragraph(item[0], col1_style), - get_unoverflowable_paragraph(getattr(misp_object, item[1]), col2_style)]) + # ---------------------------------------------------------------------- + def get_tag_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event tags + :param misp_event: A misp event with or without "tags" 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 "tags" + ''' + if is_safe_attribute_table(misp_event, item[1]): + table_event_tags = self.create_flowable_table_from_tags(misp_event) + return table_event_tags + return self.value_formatter.get_unoverflowable_paragraph(item[2]) + + def create_flowable_table_from_tags(self, misp_event): + ''' + Returns a Table (flowable) to add to a pdf, representing the list of tags of an event or a misp event + :param misp_event: A misp event + :return: a table of flowable to add to the pdf + ''' + + flowable_table = [] + i = 0 + + if is_safe_attribute_table(misp_event,"Tag"): + # There is some tags for this object + for item in getattr(misp_event, "Tag"): + flowable_table.append(create_flowable_tag(item)) + i += 1 + answer_tags = self.create_tags_table_from_data(flowable_table) + else: + # No tags for this object + answer_tags = [self.value_formatter.get_unoverflowable_paragraph("No tags")] + + return answer_tags + + def create_tags_table_from_data(self, data): + ''' + Given a list of flowables tags (2D/list of list), creates a Table with styles adapted to tags. + :param data: list of list of tags (flowables) + :return: a Table - with styles - to add to another table + ''' + + # Create the table + curr_table = Table(data, COL_WIDTHS, rowHeights=ROW_HEIGHT_FOR_TAGS) + + # Create styles and set parameters + general_style = general_style_generator() + + # Make the table nicer + curr_table.setStyle(TableStyle(general_style)) + + return curr_table + + +class Sightings(): + + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.value_formatter = value_formatter + + # ---------------------------------------------------------------------- + + def create_flowable_paragraph_from_sightings(self, misp_attribute, item): + ''' + Returns a Table (flowable) to add to a pdf, representing the list of sightings of an event or a misp event + :param misp_event: A misp event + :return: a table of flowable to add to the pdf + ''' + + col1_style, col2_style = get_table_styles() + i = 0 + POSITIVE_SIGHT_COLOR = 'green' + NEGATIVE_SIGHT_COLOR = 'red' + MISC_SIGHT_COLOR = 'orange' + + list_sighting = [0, 0, 0] + if is_safe_attribute_table(misp_attribute, item[1]): + # There is some tags for this object + for curr_item in getattr(misp_attribute, item[1]): + # TODO : When Sightings will be object : if is_safe_attribute(item, "type"): + if is_safe_dict_attribute(curr_item,"type"): + # Store the likes/dislikes depending on their types + list_sighting[int(curr_item["type"])] += 1 + i += 1 + + # Create the sighting text + sight_text = " Positive : " + str(list_sighting[0]) + "" + sight_text += " / " + " Negative : " + str( + list_sighting[1]) + "" + sight_text += " / " + " Misc. : " + str(list_sighting[2]) + "" + + answer_sighting = self.value_formatter.get_unoverflowable_paragraph(sight_text) + else: + # No tags for this object + answer_sighting = self.value_formatter.get_unoverflowable_paragraph("No sighting") + + return answer_sighting + +class Object(): + + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.value_formatter = value_formatter + self.sample_style_sheet = getSampleStyleSheet() + + # ---------------------------------------------------------------------- + + def create_flowable_table_from_objects(self, misp_event, config=None): + ''' + Returns a list of flowables representing the list of objects of a misp event. + The list is composed of a serie of + [ header object, table of object information, [ header of attribute, table of attribute]*] to add to the pdf + :param misp_event: A misp event + :return: a table of flowables + ''' + + flowable_table = [] + i = 0 + + if is_safe_attribute_table(misp_event, "Object"): + + # There is a list of objects + 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(Paragraph("Object #" + str(i), self.sample_style_sheet['Heading3'])) + flowable_table += self.create_flowable_table_from_one_object(item, config) + i += 1 + else: + # No object found + flowable_table.append(Paragraph("No object", self.sample_style_sheet['Heading3'])) + + return flowable_table + + def create_flowable_table_from_one_object(self, misp_object, config=None): + ''' + Returns a table (flowable) representing the object + :param misp_attribute: A misp object + :return: a table representing this misp's object's attributes, to add to the pdf as a flowable + ''' + data = [] + col1_style, col2_style = get_table_styles() + + # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, + # to_display_if_not_present) are store in the following list + list_attr_automated = [["UUID", 'uuid', "None"], + ["Description", 'description', "None"], + ["Meta Category", 'meta-category', "None"], + ["Object Name", 'name', "None"], + ["Comment", 'comment', "None"], + ["Type", 'type', "None"]] + + # Automated adding of standard (python) attributes of the misp object + for item in list_attr_automated: + if is_safe_attribute(misp_object, item[1]): + # The attribute exists, we fetch it and create the row + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_unoverflowable_paragraph(getattr(misp_object, item[1]))]) + + # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : + # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) + + # Timestamp + item = ["Object date", 'timestamp', "None"] + data.append([Paragraph(item[0], col1_style), self.value_formatter.get_timestamp_value(misp_object, item)]) + + # Transform list of value in a table + data = [create_flowable_table_from_data(data)] + + # Handle all the attributes + if is_safe_attribute(misp_object, "Attribute"): + curr_attributes = Attributes(self.config, self.value_formatter) + data += curr_attributes.create_flowable_table_from_attributes(misp_object) + + # Add a page break at the end of an object + data.append(PageBreak()) + + return data + + +class Galaxy(): + + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.value_formatter = value_formatter + + # ---------------------------------------------------------------------- + + def get_galaxy_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event galaxies + :param misp_event: A misp event with or without "galaxies" attributes + :param item: a list of name, in order : + ["Name to be print in the pdf", "json property access name", + " Name to be display if no values found in the misp_event"] + :param col2_style: style to be applied on the returned paragraph + :return: a Paragraph to add in the pdf, regarding the values of "galaxies" + ''' + if is_safe_attribute_table(misp_event, item[1]): + table_event_tags = self.create_flowable_table_from_galaxies(misp_event) + return table_event_tags + return self.value_formatter.get_unoverflowable_paragraph(item[2]) + + def create_flowable_table_from_galaxies(self, misp_event): + ''' + Returns a Table (flowable) to add to a pdf, representing the list of galaxies of an event or a misp event + :param misp_event: A misp event + :return: a table of flowables to add to the pdf + ''' + + flowable_table = [] + col1_style, col2_style = get_table_styles() + i = 0 + + if is_safe_attribute_table(misp_event, "Galaxy"): + # There is some galaxies for this object + for item in getattr(misp_event, "Galaxy"): + # flowable_table.append([get_unoverflowable_paragraph(item["name"],col2_style)]) + flowable_table.append(self.create_flowable_table_from_one_galaxy(item)) + i += 1 + answer_tags = create_flowable_table_from_data(flowable_table, ["99%"]) + else: + # No galaxies for this object + answer_tags = [Paragraph("No galaxies", col2_style)] + + return answer_tags + + + def create_flowable_table_from_one_galaxy(self, misp_galaxy): + ''' + Returns a table (flowable) representing the galaxy + :param misp_attribute: A misp galaxy + :return: a table representing this misp's galaxy's attributes, to add to the pdf as a flowable + ''' + data = [] + col1_style, col2_style = get_table_styles() + + # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, + # to_display_if_not_present) are store in the following list + list_attr_automated = [["Name", 'name', "None"], + ["Type", 'type', "None"], + ["Description", 'description', "None"], + ["NameSpace", 'namespace', "None"]] + + # Automated adding of standard (python) attributes of the misp object + for item in list_attr_automated: + if is_safe_dict_attribute(misp_galaxy, item[1]): + # The attribute exists, we fetch it and create the row + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_unoverflowable_paragraph(misp_galaxy[item[1]])]) + + # Clusters + item = ["Clusters", 'GalaxyCluster', "None"] + # data.append([Paragraph(item[0], col1_style), create_flowable_table_from_galaxy_clusters(misp_galaxy)]) + + tmp_table = Table(data, ["25%", "75%"]) # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) + return [tmp_table] - # Timestamp - item = ["Object date", 'timestamp', "None"] - data.append([Paragraph(item[0], col1_style), get_timestamp_value(misp_object, item, col2_style)]) - # Transform list of value in a table - data = [create_flowable_table_from_data(data)] +class Galaxy_cluster(): - # Handle all the attributes - if is_safe_attribute(misp_object, "Attribute"): - data += create_flowable_table_from_attributes(misp_object, config) + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.value_formatter = value_formatter - # Add a page break at the end of an object - data.append(PageBreak()) + # ---------------------------------------------------------------------- + def create_flowable_table_from_galaxy_clusters(self, misp_event): + ''' + Returns a Table (flowable) to add to a pdf, representing the list of galaxy clusters of a galaxy + :param misp_event: A misp event + :return: a table of flowables to add to the pdf + ''' - return data + flowable_table = [] + i = 0 -def create_flowable_table_from_one_galaxy(misp_galaxy): - ''' - Returns a table (flowable) representing the galaxy - :param misp_attribute: A misp galaxy - :return: a table representing this misp's galaxy's attributes, to add to the pdf as a flowable - ''' - data = [] - col1_style, col2_style = get_table_styles() + if is_safe_dict_attribute(misp_event, "GalaxyCluster"): + # There is some galaxies for this object + for item in misp_event["GalaxyCluster"]: + # flowable_table.append([get_unoverflowable_paragraph(item["name"],col2_style)]) + flowable_table.append(self.create_flowable_table_from_one_galaxy_cluster(item)) + i += 1 + answer_tags = create_flowable_table_from_data(flowable_table, ["99%"]) + else: + # No galaxies for this object + answer_tags = [self.value_formatter.get_unoverflowable_paragraph("No galaxy cluster")] - # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, - # to_display_if_not_present) are store in the following list - list_attr_automated = [["Name", 'name', "None"], - ["Type", 'type', "None"], - ["Description", 'description', "None"], - ["NameSpace", 'namespace', "None"]] + return answer_tags - # Automated adding of standard (python) attributes of the misp object - for item in list_attr_automated: - if is_safe_dict_attribute(misp_galaxy, item[1]): - # The attribute exists, we fetch it and create the row - data.append([Paragraph(item[0], col1_style), - get_unoverflowable_paragraph(misp_galaxy[item[1]], col2_style)]) + def create_flowable_table_from_one_galaxy_cluster(self, misp_cluster): + ''' + Returns a table (flowable) representing the galaxy + :param misp_attribute: A misp galaxy + :return: a table representing this misp's galaxy's attributes, to add to the pdf as a flowable + ''' + data = [] - # Clusters - item = ["Clusters", 'GalaxyCluster', "None"] - data.append([Paragraph(item[0], col1_style), create_flowable_table_from_one_galaxy_cluster(misp_galaxy, item, col2_style)]) + # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, + # to_display_if_not_present) are store in the following list + list_attr_automated = [["Name", 'name', "None"], + ["Type", 'type', "None"], + ["Description", 'description', "None"], + ["NameSpace", 'namespace', "None"]] - tmp_table = Table(data, ["25%","75%"]) + # Automated adding of standard (python) attributes of the misp object + for item in list_attr_automated: + if is_safe_dict_attribute(misp_cluster, item[1]): + # The attribute exists, we fetch it and create the row + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_unoverflowable_paragraph(misp_cluster[item[1]])]) + + tmp_table = Table(data, ["25%", "75%"]) + print(tmp_table) # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) - return [tmp_table] + return [tmp_table] -def create_flowable_table_from_one_galaxy_cluster(misp_galaxy): - ''' - Returns a table (flowable) representing the galaxy - :param misp_attribute: A misp galaxy - :return: a table representing this misp's galaxy's attributes, to add to the pdf as a flowable - ''' - data = [] - col1_style, col2_style = get_table_styles() - # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, - # to_display_if_not_present) are store in the following list - list_attr_automated = [["Name", 'name', "None"], - ["Type", 'type', "None"], - ["Description", 'description', "None"], - ["NameSpace", 'namespace', "None"]] - - # Automated adding of standard (python) attributes of the misp object - for item in list_attr_automated: - if is_safe_dict_attribute(misp_galaxy, item[1]): - # The attribute exists, we fetch it and create the row - data.append([Paragraph(item[0], col1_style), - get_unoverflowable_paragraph(misp_galaxy[item[1]], col2_style)]) - - # Clusters - item = ["Object date", 'timestamp', "None"] - data.append([Paragraph(item[0], col1_style), get_timestamp_value(misp_object, item, col2_style)]) - - - tmp_table = Table(data, ["25%","75%"]) - # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : - # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) - return [tmp_table] - -def get_image_value(misp_attribute, item, col2_style): - ''' - Returns a flowable image to add to the pdf given the misp attribute type and data - :param misp_attribute: A misp attribute with type="attachement" and data - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a flowable image to add in the pdf, regarding the values of "data" - ''' - - try: - # Get the image - buf = getattr(misp_attribute, item[1]) - - # Create image within a bounded box (to allow pdf creation) - img = Image(buf, width=FRAME_PICTURE_MAX_WIDTH, height=FRAME_PICTURE_MAX_HEIGHT, kind='bound') - answer = img - except OSError: - logger.error( - "Trying to add an attachment during PDF export generation. Attachement joining failed. Attachement may not be an image.") - answer = get_unoverflowable_paragraph( - "" + NOT_A_PICTURE_MESSAGE + "", col2_style, False) - - return answer - - - - -######################################################################## -# General Event's Attributes formater tools - -def uuid_to_url(baseurl, uuid): - ''' - Return an url constructed from the MISP baseurl and the uuid of the event, to go to this event on this MISP - :param baseurl: the baseurl of the MISP instnce e.g. http://localhost:8080 or http://localhost:8080/ - :param uuid: the uuid of the event that we want to have a link to - :return: the complete URL to go to this event on this MISP instance - ''' - if baseurl[len(baseurl) - 1] != "/": - baseurl += "/" - return baseurl + "events/view/" + uuid - - -def create_flowable_table_from_data(data, col_w = COL_WIDTHS): - ''' - 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) - :return: a Table - with styles - to add to the pdf - ''' - # Create the table - curr_table = Table(data, col_w) - - # Aside notes : - # colWidths='*' does a 100% and share the space automatically - # 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) - lines_style = lines_style_generator(data) - general_style = general_style_generator() - - # Make the table nicer - curr_table.setStyle(TableStyle(general_style + alternate_colors_style + lines_style)) - - return curr_table - -def create_tags_table_from_data(data): - ''' - Given a list of flowables tags (2D/list of list), creates a Table with styles adapted to tags. - :param data: list of list of tags (flowables) - :return: a Table - with styles - to add to another table - ''' - - # Create the table - curr_table = Table(data, COL_WIDTHS, rowHeights=ROW_HEIGHT_FOR_TAGS) - - # Create styles and set parameters - general_style = general_style_generator() - - # Make the table nicer - curr_table.setStyle(TableStyle(general_style)) - - return curr_table - - -def get_good_link(misp_attribute, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_attribute value, if this is a link - :param misp_attribute: A misp attribute with a link - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of this "link" attribute - ''' - return get_unoverflowable_paragraph( - "" + getattr( - misp_attribute, item[1]) + "", col2_style, False) - - -def get_bad_link(misp_attribute, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_attribute value, if this is a link - :param misp_attribute: A misp event with an url - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of this "url" attribute - ''' - return get_unoverflowable_paragraph( - "" + getattr(misp_attribute, - item[1]) + "", - col2_style, False) - - -def get_good_or_bad_link(misp_attribute, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_attribute value, if this is a link or an url - :param misp_attribute: A misp attribute with a link or an url - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of this "link" or "url" attribute - ''' - - # Handle "Good" links - if getattr(misp_attribute, 'type') == LINK_TYPE: - answer = get_good_link(misp_attribute, item, col2_style) - # Handle "bad "links - elif getattr(misp_attribute, 'type') == URL_TYPE: - answer = get_bad_link(misp_attribute, item, col2_style) - - return answer - - -######################################################################## -# General Event's Attributes formater - -def create_flowable_table_from_event(misp_event, config=None): - ''' - 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 = [] - col1_style, col2_style = get_table_styles() - - # Manual addition - # UUID - item = ["UUID", 'uuid', "None"] - data.append([Paragraph(item[0], col1_style), get_value_link_to_event(misp_event, item, col2_style, config)]) - - # Date - item = ["Date", 'date', "None"] - data.append([Paragraph(item[0], col1_style), get_date_value(misp_event, item, col2_style)]) - - # Owner - item = ["Owner org", 'owner', "None"] - data.append([Paragraph(item[0], col1_style), get_owner_value(misp_event, item, col2_style)]) - - # Threat - item = ["Threat level", 'threat_level_id', "None"] - data.append([Paragraph(item[0], col1_style), get_threat_value(misp_event, item, col2_style)]) - - # Analysis - item = ["Analysis", 'analysis', "None"] - data.append([Paragraph(item[0], col1_style), get_analysis_value(misp_event, item, col2_style)]) - - # Info - item = ["Info", 'info', "None"] - data.append([Paragraph(item[0], col1_style), get_value_link_to_event(misp_event, item, col2_style, config)]) - - # Timestamp - item = ["Event date", 'timestamp', "None"] - data.append([Paragraph(item[0], col1_style), get_timestamp_value(misp_event, item, col2_style)]) - - # Published - item = ["Published", 'published', "None", "publish_timestamp"] - data.append([Paragraph(item[0], col1_style), get_published_value(misp_event, item, col2_style)]) - - # Creator organisation - item = ["Creator Org", 'Orgc', "None", "name"] - data.append([Paragraph(item[0], col1_style), get_creator_organisation_value(misp_event, item, col2_style)]) - - # Number of Attributes - item = ["# Attributes", 'Attribute', "None"] - data.append([Paragraph(item[0], col1_style), get_attributes_number_value(misp_event, item, col2_style)]) - - # Tags - item = ["Tags", 'Tag', "None"] - data.append([Paragraph(item[0], col1_style), get_tag_value(misp_event, item, col2_style)]) - - return create_flowable_table_from_data(data) - - -def create_flowable_description_from_event(misp_event, config=None): - ''' - Returns a Paragraph presenting a MISP event - :param misp_event: A misp event (complete or not) - :return: a paragraph that can be added to a pdf - ''' - - ''' - The event "{EventName}" | that occurred on {EventDate}, | had been shared by {Organisation Name} | on the {Date}. - ''' - - text = "" - - item = ["Info", 'info', "None"] - if is_safe_attribute(misp_event, item[1]): - text += "The event '" - text += str(getattr(misp_event, item[1])) - text += "'" - else: - text += "This event" - - item = ["Event date", 'timestamp', "None"] - if is_safe_attribute(misp_event, item[1]): - text += " that occurred on " - text += str(getattr(misp_event, item[1]).strftime(EXPORT_DATE_FORMAT)) - text += "," - - item = ["Creator Org", 'Orgc', "None", "name"] - text += " had been shared by " - if is_safe_attribute(misp_event, item[1]): - text += safe_string(getattr(getattr(misp_event, item[1]), item[3])) - else: - text += " an unknown organisation" - - item = ["Date", 'date', "None"] - if is_safe_attribute(misp_event, item[1]): - text += " on the " - text += str(getattr(misp_event, item[1])) - else: - text += " on an unknown date" - text += "." - - ''' - The threat level of this event is {ThreatLevel} and the analysis that was made of this event is {AnalysisLevel}. - ''' - - item = ["Threat level", 'threat_level_id', "None"] - text += " The threat level of this event is " - if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in threat_map: - text += threat_map[str(getattr(misp_event, item[1]))] - else: - text += " unknown" - - item = ["Analysis", 'analysis', "None"] - text += " and the analysis that was made of this event is " - if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in analysis_map: - text += analysis_map[str(getattr(misp_event, item[1]))] - else: - text += " undefined" - text += "." - - ''' - The event is currently {Published} and has associated attributes {Attribute Number}. - ''' - - item = ["Published", 'published', "None", "publish_timestamp"] - text += " The event is currently " - if is_safe_attribute(misp_event, item[1]) and getattr(misp_event, item[1]): - text += " published" - if is_safe_attribute(misp_event, item[3]): - text += " since " + getattr(misp_event, item[3]).strftime(EXPORT_DATE_FORMAT) - else: - text += " private" - - # Number of Attributes - item = ["# Attributes", 'Attribute', "None"] - text += ", has " - if is_safe_attribute_table(misp_event, item[1]): - text += str(len(getattr(misp_event, item[1]))) - else: - text += " 0" - - text += " associated attributes" - - # Number of Objects - item = ["# Objects", 'Object', "None"] - text += " and has " - if is_safe_attribute_table(misp_event, item[1]): - text += str(len(getattr(misp_event, item[1]))) - else: - text += " 0" - - text += " associated objects." - - ''' - For more information on the event, please consult the rest of the document - ''' - text += "
For more information on the event, please consult following information." - - col1_style, col2_style = get_table_styles() - description_style = ParagraphStyle(name='Description', parent=col2_style, alignment=TA_JUSTIFY) - - return Paragraph(text, description_style) - - -def create_flowable_table_from_attributes(misp_event, config=None): - ''' - Returns a list of flowables representing the list of attributes of a misp event. - The list is composed alternatively of headers and tables, to add to the pdf - :param misp_event: A misp event - :return: a table of flowables - ''' - flowable_table = [] - sample_style_sheet = getSampleStyleSheet() - i = 0 - - if is_safe_attribute_table(misp_event, "Attribute"): - # There is some attributes for this object - 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(Paragraph("Attribute #" + str(i), sample_style_sheet['Heading4'])) - flowable_table.append(create_flowable_table_from_one_attribute(item, config)) - i += 1 - else: - # No attributes for this object - flowable_table.append(Paragraph("No attributes", sample_style_sheet['Heading4'])) - - return flowable_table - - -def create_flowable_table_from_tags(misp_event): - ''' - Returns a Table (flowable) to add to a pdf, representing the list of tags of an event or a misp event - :param misp_event: A misp event - :return: a table of flowable to add to the pdf - ''' - - flowable_table = [] - col1_style, col2_style = get_table_styles() - i = 0 - - if is_safe_attribute_table(misp_event, - "Tag"): # and len(getattr(misp_event, "Tag")) > 1: # 'Tag' can exist and be empty - # There is some tags for this object - for item in getattr(misp_event, "Tag"): - flowable_table.append(create_flowable_tag(item)) - i += 1 - answer_tags = create_tags_table_from_data(flowable_table) - else: - # No tags for this object - answer_tags = [Paragraph("No tags", col2_style)] - - return answer_tags - -def create_flowable_table_from_objects(misp_event, config=None): - ''' - Returns a list of flowables representing the list of objects of a misp event. - The list is composed of a serie of - [ header object, table of object information, [ header of attribute, table of attribute]*] to add to the pdf - :param misp_event: A misp event - :return: a table of flowables - ''' - - flowable_table = [] - sample_style_sheet = getSampleStyleSheet() - i = 0 - - if is_safe_attribute_table(misp_event, "Object"): - - # There is a list of objects - 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(Paragraph("Object #" + str(i), sample_style_sheet['Heading3'])) - flowable_table += create_flowable_table_from_one_object(item, config) - i += 1 - else: - # No object found - flowable_table.append(Paragraph("No object", sample_style_sheet['Heading3'])) - - return flowable_table - - -def create_flowable_paragraph_from_sightings(misp_attribute, item, col2_style): - ''' - Returns a Table (flowable) to add to a pdf, representing the list of sightings of an event or a misp event - :param misp_event: A misp event - :return: a table of flowable to add to the pdf - ''' - - col1_style, col2_style = get_table_styles() - i = 0 - POSITIVE_SIGHT_COLOR = 'green' - NEGATIVE_SIGHT_COLOR = 'red' - MISC_SIGHT_COLOR = 'orange' - - list_sighting = [0, 0, 0] - if is_safe_attribute_table(misp_attribute, "Sighting"): - # There is some tags for this object - for curr_item in getattr(misp_attribute, "Sighting"): - # TODO : When Sightings will be object : if is_safe_attribute(item, "type"): - if "type" in curr_item: - # Store the likes/dislikes depending on their types - list_sighting[int(curr_item["type"])] += 1 - i += 1 - - # Create the sighting text - sight_text = " Positive : " + str(list_sighting[0]) + "" - sight_text += " / " + " Negative : " + str( - list_sighting[1]) + "" - sight_text += " / " + " Misc. : " + str(list_sighting[2]) + "" - - answer_sighting = Paragraph(sight_text, col2_style) - else: - # No tags for this object - answer_sighting = Paragraph("No sighting", col2_style) - - return answer_sighting - - -def create_flowable_table_from_galaxies(misp_event): - ''' - Returns a Table (flowable) to add to a pdf, representing the list of galaxies of an event or a misp event - :param misp_event: A misp event - :return: a table of flowables to add to the pdf - ''' - - flowable_table = [] - col1_style, col2_style = get_table_styles() - i = 0 - - if is_safe_attribute_table(misp_event, "Galaxy"): - # There is some galaxies for this object - for item in getattr(misp_event, "Galaxy"): - # flowable_table.append([get_unoverflowable_paragraph(item["name"],col2_style)]) - flowable_table.append(create_flowable_table_from_one_galaxy(item)) - i += 1 - answer_tags = create_flowable_table_from_data(flowable_table, ["99%"]) - else: - # No galaxies for this object - answer_tags = [Paragraph("No galaxies", col2_style)] - - return answer_tags ######################################################################## # Handling static parts drawn on the upper layer -def set_template(canvas, doc, misp_event, config=None): - add_page_number(canvas, doc) - add_metadata(canvas, doc, misp_event, config) - # TODO : add_header() - # TODO : add_footer() -def add_metadata(canvas, doc, misp_event, config=None): - ''' - Allow to add metadata to the pdf. Would need deeper digging to change other metadata. - :param canvas: / Automatically filled during pdf compilation - :param doc: / Automatically filled during pdf compilation - :param misp_event: To send trough "partial", to get information to complete metadaa - :return: / Automatically filled during pdf compilation - ''' +class Statics_Drawings(): - if hasattr(misp_event, 'info'): - canvas.setTitle(getattr(misp_event, 'info')) + # ---------------------------------------------------------------------- + def __init__(self, config, misp_event): + self.config = config + self.misp_event = misp_event - if hasattr(misp_event, 'info'): - canvas.setSubject(getattr(misp_event, 'info')) + # ---------------------------------------------------------------------- - if hasattr(misp_event, 'Orgc'): - if hasattr(getattr(misp_event, 'Orgc'), 'name'): - canvas.setAuthor(getattr(getattr(misp_event, 'Orgc'), 'name')) - - if config is not None and moduleconfig[1] in config: - canvas.setCreator(config[moduleconfig[1]]) - else: - canvas.setCreator(getattr(getattr(misp_event, 'Orgc'), 'name')) - - if hasattr(misp_event, 'uuid'): - canvas.setKeywords(getattr(misp_event, 'uuid')) + def set_template(self, canvas, doc): + self.add_page_number(canvas, doc) + self.add_metadata(canvas, doc) + # TODO : add_header() + # TODO : add_footer() -def add_page_number(canvas, doc): - ''' - Draw the page number on each page - :param canvas: / Automatically filled during pdf compilation - :param doc: / Automatically filled during pdf compilation - :return: / Automatically filled during pdf compilation - ''' - canvas.saveState() - canvas.setFont('Times-Roman', 10) - page_number_text = "%d" % (doc.page) + def add_metadata(self,canvas, doc): + ''' + Allow to add metadata to the pdf. Would need deeper digging to change other metadata. + :param canvas: / Automatically filled during pdf compilation + :param doc: / Automatically filled during pdf compilation + :param misp_event: To send trough "partial", to get information to complete metadaa + :return: / Automatically filled during pdf compilation + ''' - curr_spacing = 4 * mm # 0.75 * inch + if is_safe_attribute(self.misp_event, 'info'): + canvas.setTitle(getattr(self.misp_event, 'info')) - canvas.drawCentredString( - curr_spacing, - curr_spacing, - page_number_text - ) + if is_safe_attribute(self.misp_event, 'info'): + canvas.setSubject(getattr(self.misp_event, 'info')) - canvas.restoreState() + if is_safe_attribute(self.misp_event, 'Orgc'): + if is_safe_attribute(getattr(self.misp_event, 'Orgc'), 'name'): + canvas.setAuthor(getattr(getattr(self.misp_event, 'Orgc'), 'name')) + + if is_in_config(self.config,1) : + canvas.setCreator(self.config[moduleconfig[1]]) + else: + canvas.setCreator(getattr(getattr(self.misp_event, 'Orgc'), 'name')) + + if is_safe_attribute(self.misp_event, 'uuid'): + canvas.setKeywords(getattr(self.misp_event, 'uuid')) + + + def add_page_number(self,canvas, doc): + ''' + Draw the page number on each page + :param canvas: / Automatically filled during pdf compilation + :param doc: / Automatically filled during pdf compilation + :return: / Automatically filled during pdf compilation + ''' + canvas.saveState() + canvas.setFont('Times-Roman', 10) + page_number_text = "%d" % (doc.page) + + curr_spacing = 4 * mm # 0.75 * inch + + canvas.drawCentredString( + curr_spacing, + curr_spacing, + page_number_text + ) + + canvas.restoreState() ######################################################################## @@ -1285,37 +1384,45 @@ def collect_parts(misp_event, config=None): ''' # List of elements/content we want to add flowables = [] + # Get the list of available styles sample_style_sheet = getSampleStyleSheet() + col1_style, col2_style = get_table_styles() + curr_val_f = Value_Formatter(config, col1_style, col2_style) # Create stuff title_style = ParagraphStyle(name='Column_1', parent=sample_style_sheet['Heading1'], alignment=TA_CENTER) - title = get_value_link_to_event(misp_event, ["Info", 'info', "None"], title_style, config, False) + 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) - if config is not None and moduleconfig[2] in config: + # Creation of handling objects + curr_event = Event_Metadata(config, curr_val_f) + curr_attr = Attributes(config, curr_val_f) + curr_object = Object(config, curr_val_f) + + if is_in_config(config,2) : # If description is activated description = Paragraph("Description", sample_style_sheet['Heading2']) - description_text = create_flowable_description_from_event(misp_event, config) + description_text = curr_event.create_flowable_description_from_event(misp_event) flowables.append(description) flowables.append(description_text) subtitle = Paragraph("General information", sample_style_sheet['Heading2']) - table_general_metainformation = create_flowable_table_from_event(misp_event, config) + table_general_metainformation = curr_event.create_flowable_table_from_event(misp_event) flowables.append(subtitle) flowables.append(table_general_metainformation) flowables.append(PageBreak()) event_attributes_title = Paragraph("Attributes", sample_style_sheet['Heading2']) - table_direct_attributes = create_flowable_table_from_attributes(misp_event, config) + table_direct_attributes = curr_attr.create_flowable_table_from_attributes(misp_event) flowables.append(event_attributes_title) flowables += table_direct_attributes flowables.append(PageBreak()) event_objects_title = Paragraph("Objects", sample_style_sheet['Heading2']) - table_objects = create_flowable_table_from_objects(misp_event, config) + table_objects = curr_object.create_flowable_table_from_objects(misp_event) flowables.append(event_objects_title) flowables += table_objects @@ -1325,7 +1432,7 @@ def collect_parts(misp_event, config=None): return flowables -def export_flowables_to_pdf(document, misp_event, flowables, config=None): +def export_flowables_to_pdf(document, misp_event, flowables, config): ''' Export function : creates a pdf from a list of flowables, adding page numbers, etc. :param document: A document template @@ -1334,13 +1441,15 @@ def export_flowables_to_pdf(document, misp_event, flowables, config=None): :return: ''' + static_drawer = Statics_Drawings(config, misp_event) + document.build( flowables, # Partial used to set the metadata - onFirstPage=partial(set_template, misp_event=misp_event, config=config), # Pagination for first page - onLaterPages=partial(set_template, misp_event=misp_event, config=config), # Pagination for all other page + onFirstPage=static_drawer.set_template, # Pagination for first page + onLaterPages=static_drawer.set_template, # Pagination for all other page ) - + # Old way : onLaterPages=partial(static_drawer.set_template, misp_event=misp_event), # Pagination for all other page ######################################################################## # "EXTERNAL" exposed METHODS. Meant to be used outside of this class. @@ -1375,6 +1484,7 @@ def convert_event_in_pdf_buffer(misp_event, config=None): return pdf_value + def get_values_from_buffer(pdf_buffer): return pdf_buffer.value() From 79e66363d23e643c842c746cbd3211d550bf4c5c Mon Sep 17 00:00:00 2001 From: Falconieri Date: Thu, 28 Feb 2019 15:14:52 +0100 Subject: [PATCH 03/10] fix: [reportlab] working clusters and galaxies. Not nice however --- pymisp/tools/reportlab_generator.py | 317 +++++++++++++++++----------- 1 file changed, 192 insertions(+), 125 deletions(-) diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index eb5f6a7..cd418e9 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -148,6 +148,7 @@ TEXT_FONT_SIZE = 8 LEADING_SPACE = 7 EXPORT_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' COL_WIDTHS = ['30%', '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) @@ -234,7 +235,7 @@ def uuid_to_url(baseurl, uuid): return baseurl + "events/view/" + uuid -def create_flowable_table_from_data(data, col_w=COL_WIDTHS): +def create_flowable_table_from_data(data, col_w=COL_WIDTHS, color_alternation=None, line_alternation=None): ''' 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) @@ -248,8 +249,8 @@ def create_flowable_table_from_data(data, col_w=COL_WIDTHS): # 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) - lines_style = lines_style_generator(data) + alternate_colors_style = alternate_colors_style_generator(data,color_alternation) + lines_style = lines_style_generator(data,line_alternation) general_style = general_style_generator() # Make the table nicer @@ -258,11 +259,13 @@ def create_flowable_table_from_data(data, col_w=COL_WIDTHS): return curr_table -def alternate_colors_style_generator(data): +def alternate_colors_style_generator(data, color_alternation): ''' Create a style, applicable on a table that will be built with parameter's data, with alternated background color for each line. Modified from : https://gist.github.com/chadcooper/5798392 + :param color_alternation: Allow to control the color scheme. e.g. [0,0,0,1,1,0 ... will produce 3 lines of a color, + 2 lines of another, 1 of the first one ... :param data: list of list of items (2D table) to be displayed in the pdf :return: A list of 'BACKGROUND' properties, usable in a TableStyle, with alternated colours ''' @@ -270,33 +273,60 @@ def alternate_colors_style_generator(data): data_len = len(data) color_list = [] - # For each line, generate a tuple giving to a line a color - for each in range(data_len): - if each % 2 == 0: - bg_color = EVEN_COLOR - else: - bg_color = ODD_COLOR - color_list.append(('BACKGROUND', (0, each), (-1, each), bg_color)) + if color_alternation is None: + # For each line, generate a tuple giving to a line a color + for each in range(data_len): + if each % 2 == 0: + bg_color = EVEN_COLOR + else: + bg_color = ODD_COLOR + color_list.append(('BACKGROUND', (0, each), (-1, each), bg_color)) + else: + if data_len > len(color_alternation) : + logger.warning("Line alternation for PDF display isn't correctly set. Looping on given values only.") + + # 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: + bg_color = EVEN_COLOR + else: + bg_color = ODD_COLOR + color_list.append(('BACKGROUND', (0, each), (-1, each), bg_color)) return color_list -def lines_style_generator(data): +def lines_style_generator(data, line_alternation): ''' Create a style, applicable on a table that will be built with parameter's data, that draw colored lines above and below each line of the table + :param line_alternation: Allow to control the color scheme. e.g. [0,0,0,1,1,0 ... will produce with a line up it, + 2 lines without, 1 of the first one ... :param data: list of list of items (2D table) to be displayed in the pdf :return: A list of 'LINE****' properties, usable in a TableStyle, that are drawing lines ''' data_len = len(data) lines_list = [] - # For each line, generate a tuple giving to a line a color - for each in range(data_len): - lines_list.append(('LINEABOVE', (0, each), (-1, each), LINE_THICKNESS, LINE_COLOR)) + if line_alternation is None: + # For each line, generate a tuple giving to a line a color + for each in range(data_len): + 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)) + else: + if data_len > len(line_alternation) : + logger.warning("Line alternation for PDF display isn't correctly set. Looping on given values only.") + + # 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)]: + 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)) - # Last line - lines_list.append(('LINEBELOW', (0, len(data) - 1), (-1, len(data) - 1), LINE_THICKNESS, LINE_COLOR)) return lines_list @@ -366,9 +396,11 @@ def is_safe_attribute_table(curr_object, attribute_name): return hasattr(curr_object, attribute_name) and getattr(curr_object, attribute_name) is not None and getattr( curr_object, attribute_name) != [] + def is_in_config(config, index): return config is not None and moduleconfig[index] in config + ######################################################################## # Functions grouped by misp object type @@ -379,6 +411,7 @@ class Value_Formatter(): ["Name to be print in the pdf", "json property access name", " Name to be display if no values found in the misp_event"] ''' + # ---------------------------------------------------------------------- def __init__(self, config, col1_style, col2_style): self.config = config @@ -391,7 +424,7 @@ class Value_Formatter(): def get_col1_paragraph(self, dirty_string): return self.get_unoverflowable_paragraph(dirty_string, self.col1_style) - def get_unoverflowable_paragraph(self, dirty_string, curr_style = None, do_escape_string=True): + def get_unoverflowable_paragraph(self, dirty_string, curr_style=None, do_escape_string=True): ''' Create a paragraph that can fit on a cell displayed one page maximum. This method can be improved (get the exact size of the current frame, and limit the paragraph to this size.) @@ -425,7 +458,8 @@ class Value_Formatter(): while (w > FRAME_MAX_WIDTH or h > FRAME_MAX_HEIGHT) and i < MAX_ITERATION: i += 1 limited_string = sanitized_str[:max_carac_amount] # .replace("\n", "").replace("\r", "") - w, h = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style).wrap(FRAME_MAX_WIDTH, FRAME_MAX_HEIGHT) + w, h = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style).wrap(FRAME_MAX_WIDTH, + FRAME_MAX_HEIGHT) max_carac_amount = int(max_carac_amount / 2) if w <= FRAME_MAX_WIDTH and h <= FRAME_MAX_HEIGHT: @@ -436,8 +470,7 @@ class Value_Formatter(): return answer_paragraph - - def get_value_link_to_event(self, misp_event, item, curr_style = None, color=True): + def get_value_link_to_event(self, misp_event, item, curr_style=None, color=True): ''' Returns a flowable paragraph to add to the pdf given the misp_event uuid, with or without link :param color: Boolean to give a color or not to the generate link (good link color) @@ -456,7 +489,7 @@ class Value_Formatter(): # It has the requested attribute .. building upon it. # Does misp_object has an uuid and do we know the baseurl ? - if is_safe_attribute(misp_event, "uuid") and is_in_config(self.config,0): + if is_safe_attribute(misp_event, "uuid") and is_in_config(self.config, 0): # We can build links curr_uuid = str(getattr(misp_event, "uuid")) curr_baseurl = self.config[moduleconfig[0]] @@ -495,7 +528,6 @@ class Value_Formatter(): return self.get_unoverflowable_paragraph(safe_string(getattr(misp_event, item[1]))) return self.get_unoverflowable_paragraph(item[2]) - def get_owner_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event owner @@ -508,7 +540,6 @@ class Value_Formatter(): return self.get_unoverflowable_paragraph(safe_string(getattr(misp_event, item[1]))) return self.get_unoverflowable_paragraph(item[2]) - def get_threat_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event threat @@ -518,10 +549,10 @@ class Value_Formatter(): :return: a Paragraph to add in the pdf, regarding the values of "threat" ''' if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in threat_map: - return self.get_unoverflowable_paragraph(threat_map[safe_string(getattr(misp_event, item[1]))], do_escape_string=False) + return self.get_unoverflowable_paragraph(threat_map[safe_string(getattr(misp_event, item[1]))], + do_escape_string=False) return self.get_unoverflowable_paragraph(item[2]) - def get_analysis_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event analysis @@ -531,10 +562,10 @@ class Value_Formatter(): :return: a Paragraph to add in the pdf, regarding the values of "analysis" ''' if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in analysis_map: - return self.get_unoverflowable_paragraph(analysis_map[safe_string(getattr(misp_event, item[1]))], do_escape_string=False) + return self.get_unoverflowable_paragraph(analysis_map[safe_string(getattr(misp_event, item[1]))], + do_escape_string=False) return self.get_unoverflowable_paragraph(item[2]) - def get_timestamp_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event timestamp @@ -544,10 +575,10 @@ class Value_Formatter(): :return: a Paragraph to add in the pdf, regarding the values of "timestamp" ''' if is_safe_attribute(misp_event, item[1]): - return self.get_unoverflowable_paragraph(safe_string(getattr(misp_event, item[1]).strftime(EXPORT_DATE_FORMAT))) + return self.get_unoverflowable_paragraph( + safe_string(getattr(misp_event, item[1]).strftime(EXPORT_DATE_FORMAT))) return self.get_unoverflowable_paragraph(item[2]) - def get_creator_organisation_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event creator organisation @@ -562,7 +593,6 @@ class Value_Formatter(): return self.get_unoverflowable_paragraph(safe_string(getattr(getattr(misp_event, item[1]), item[3]))) return self.get_unoverflowable_paragraph(item[2]) - def get_attributes_number_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event attributes @@ -575,8 +605,6 @@ class Value_Formatter(): return self.get_unoverflowable_paragraph(safe_string(len(getattr(misp_event, item[1])))) return self.get_unoverflowable_paragraph(item[2]) - - def get_published_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event published/published_time @@ -601,7 +629,9 @@ class Value_Formatter(): if getattr(misp_event, item[1]): # == True if is_safe_attribute(misp_event, item[3]): # Published and have published date - answer = self.get_unoverflowable_paragraph(YES_ANSWER + getattr(misp_event, item[3]).strftime(EXPORT_DATE_FORMAT) + ")", do_escape_string=False) + answer = self.get_unoverflowable_paragraph( + YES_ANSWER + getattr(misp_event, item[3]).strftime(EXPORT_DATE_FORMAT) + ")", + do_escape_string=False) else: # Published without published date answer = self.get_unoverflowable_paragraph(YES_ANSWER + "no date)", do_escape_string=False) @@ -615,7 +645,6 @@ class Value_Formatter(): return answer - def get_image_value(self, misp_attribute, item): ''' Returns a flowable image to add to the pdf given the misp attribute type and data @@ -641,7 +670,7 @@ class Value_Formatter(): return answer - def get_good_link(self,misp_attribute, item): + def get_good_link(self, misp_attribute, item): ''' Returns a flowable paragraph to add to the pdf given the misp_attribute value, if this is a link :param misp_attribute: A misp attribute with a link @@ -653,8 +682,7 @@ class Value_Formatter(): "" + getattr( misp_attribute, item[1]) + "", do_escape_string=False) - - def get_bad_link(self,misp_attribute, item): + def get_bad_link(self, misp_attribute, item): ''' Returns a flowable paragraph to add to the pdf given the misp_attribute value, if this is a link :param misp_attribute: A misp event with an url @@ -664,10 +692,11 @@ class Value_Formatter(): ''' return self.get_unoverflowable_paragraph( "" + getattr(misp_attribute, - item[1]) + "", do_escape_string=False) + item[ + 1]) + "", + do_escape_string=False) - - def get_good_or_bad_link(self,misp_attribute, item): + def get_good_or_bad_link(self, misp_attribute, item): ''' Returns a flowable paragraph to add to the pdf given the misp_attribute value, if this is a link or an url :param misp_attribute: A misp attribute with a link or an url @@ -693,7 +722,8 @@ class Event_Metadata(): # ---------------------------------------------------------------------- def __init__(self, config, value_formatter): self.config = config - self.curr_val_f = value_formatter + self.value_formatter = value_formatter + self.sample_style_sheet = getSampleStyleSheet() # ---------------------------------------------------------------------- @@ -708,61 +738,76 @@ class Event_Metadata(): ''' data = [] + flowable_table = [] # Manual addition # UUID item = ["UUID", 'uuid', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_value_link_to_event(misp_event, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_value_link_to_event(misp_event, item)]) # Date item = ["Date", 'date', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_date_value(misp_event, item)]) + data.append( + [self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_date_value(misp_event, item)]) # Owner item = ["Owner org", 'owner', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_owner_value(misp_event, item)]) + data.append( + [self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_owner_value(misp_event, item)]) # Threat item = ["Threat level", 'threat_level_id', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_threat_value(misp_event, item)]) + data.append( + [self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_threat_value(misp_event, item)]) # Analysis item = ["Analysis", 'analysis', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_analysis_value(misp_event, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_analysis_value(misp_event, item)]) # Info item = ["Info", 'info', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_value_link_to_event(misp_event, item)]) + 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.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_timestamp_value(misp_event, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_timestamp_value(misp_event, item)]) # Published item = ["Published", 'published', "None", "publish_timestamp"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_published_value(misp_event, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_published_value(misp_event, item)]) # Creator organisation item = ["Creator Org", 'Orgc', "None", "name"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_creator_organisation_value(misp_event, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_creator_organisation_value(misp_event, item)]) # Number of Attributes item = ["# Attributes", 'Attribute', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_attributes_number_value(misp_event, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_attributes_number_value(misp_event, item)]) # Tags item = ["Tags", 'Tag', "None"] - curr_Tags = Tags(self.config, self.curr_val_f) - data.append([self.curr_val_f.get_col1_paragraph(item[0]), curr_Tags.get_tag_value(misp_event, item)]) + curr_Tags = Tags(self.config, self.value_formatter) + data.append([self.value_formatter.get_col1_paragraph(item[0]), curr_Tags.get_tag_value(misp_event, item)]) - if is_in_config(self.config, 3): - # Galaxies - item = ["Galaxies", 'Galaxy', "None"] - if is_safe_attribute_table(misp_event, item[1]): - curr_Galaxy = Galaxy(self.config, self.curr_val_f) - data.append([self.curr_val_f.get_col1_paragraph(item[0]), curr_Galaxy.get_galaxy_value(misp_event, item)]) + flowable_table.append(create_flowable_table_from_data(data)) - return create_flowable_table_from_data(data) + # Galaxies + item = ["Related Galaxies", 'Galaxy', "None"] + curr_Galaxy = Galaxy(self.config, self.value_formatter) + 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']) + + flowable_table.append(galaxy_title) + flowable_table.append(curr_Galaxy.get_galaxy_value(misp_event, item)) + + return flowable_table def create_flowable_description_from_event(self, misp_event): ''' @@ -863,12 +908,12 @@ class Event_Metadata(): ''' text += "
For more information on the event, please consult following information." - description_style = ParagraphStyle(name='Description', parent=self.curr_val_f.col2_style, alignment=TA_JUSTIFY) + description_style = ParagraphStyle(name='Description', parent=self.value_formatter.col2_style, + alignment=TA_JUSTIFY) return Paragraph(text, description_style) - class Attributes(): # ---------------------------------------------------------------------- @@ -876,6 +921,7 @@ class Attributes(): self.config = config self.value_formatter = value_formatter self.sample_style_sheet = getSampleStyleSheet() + # ---------------------------------------------------------------------- def create_flowable_table_from_attributes(self, misp_event): @@ -893,7 +939,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(Paragraph("Attribute #" + str(i), self.sample_style_sheet['Heading4'])) - flowable_table.append(self.create_flowable_table_from_one_attribute(item)) + flowable_table += self.create_flowable_table_from_one_attribute(item) i += 1 else: # No attributes for this object @@ -909,7 +955,7 @@ class Attributes(): ''' data = [] - col1_style, col2_style = get_table_styles() + flowable_table = [] # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, # to_display_if_not_present) are store in the following list @@ -939,18 +985,21 @@ class Attributes(): # Handle Special case for links (Value) - There were not written in the previous loop item = ["Value", 'value', "None"] if not STANDARD_TYPE and is_safe_attribute(misp_attribute, item[1]): - data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_good_or_bad_link(misp_attribute, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_good_or_bad_link(misp_attribute, item)]) # Handle pictures item = ["Data", 'data', "None"] if is_safe_attribute(misp_attribute, item[1]) and getattr(misp_attribute, 'type') == IMAGE_TYPE: - data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_image_value(misp_attribute, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_image_value(misp_attribute, item)]) # Tags item = ["Tags", 'Tag', "None"] curr_Tags = Tags(self.config, self.value_formatter) if is_safe_attribute_table(misp_attribute, item[1]): - data.append([self.value_formatter.get_col1_paragraph(item[0]), curr_Tags.get_tag_value(misp_attribute, item)]) + data.append( + [self.value_formatter.get_col1_paragraph(item[0]), curr_Tags.get_tag_value(misp_attribute, item)]) # Sighting item = ["Sighting", 'Sighting', "None"] @@ -959,14 +1008,18 @@ class Attributes(): data.append([self.value_formatter.get_col1_paragraph(item[0]), curr_Sighting.create_flowable_paragraph_from_sightings(misp_attribute, item)]) - if is_in_config(self.config, 3): - # Galaxies - item = ["Galaxies", 'Galaxy', "None"] - curr_Galaxy = Galaxy(self.config, self.value_formatter) - if is_safe_attribute_table(misp_attribute, item[1]): - data.append([Paragraph(item[0], col1_style), curr_Galaxy.get_galaxy_value(misp_attribute, item)]) + flowable_table.append(create_flowable_table_from_data(data)) - return create_flowable_table_from_data(data) + # Galaxies + item = ["Related Galaxies", 'Galaxy', "None"] + curr_Galaxy = Galaxy(self.config, self.value_formatter) + if is_safe_attribute_table(misp_attribute, item[1]) and is_in_config(self.config, 3): + galaxy_title = Paragraph(item[0], self.sample_style_sheet['Heading5']) + + flowable_table.append(galaxy_title) + flowable_table.append(curr_Galaxy.get_galaxy_value(misp_attribute, item)) + + return flowable_table class Tags(): @@ -1001,7 +1054,7 @@ class Tags(): flowable_table = [] i = 0 - if is_safe_attribute_table(misp_event,"Tag"): + if is_safe_attribute_table(misp_event, "Tag"): # There is some tags for this object for item in getattr(misp_event, "Tag"): flowable_table.append(create_flowable_tag(item)) @@ -1059,7 +1112,7 @@ class Sightings(): # There is some tags for this object for curr_item in getattr(misp_attribute, item[1]): # TODO : When Sightings will be object : if is_safe_attribute(item, "type"): - if is_safe_dict_attribute(curr_item,"type"): + if is_safe_dict_attribute(curr_item, "type"): # Store the likes/dislikes depending on their types list_sighting[int(curr_item["type"])] += 1 i += 1 @@ -1077,6 +1130,7 @@ class Sightings(): return answer_sighting + class Object(): # ---------------------------------------------------------------------- @@ -1176,13 +1230,13 @@ class Galaxy(): ["Name to be print in the pdf", "json property access name", " Name to be display if no values found in the misp_event"] :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "galaxies" + :return: a Flowable to add in the pdf, regarding the values of "galaxies" ''' if is_safe_attribute_table(misp_event, item[1]): - table_event_tags = self.create_flowable_table_from_galaxies(misp_event) - return table_event_tags + return self.create_flowable_table_from_galaxies(misp_event) return self.value_formatter.get_unoverflowable_paragraph(item[2]) + def create_flowable_table_from_galaxies(self, misp_event): ''' Returns a Table (flowable) to add to a pdf, representing the list of galaxies of an event or a misp event @@ -1191,23 +1245,39 @@ class Galaxy(): ''' flowable_table = [] - col1_style, col2_style = get_table_styles() - i = 0 + scheme_alternation = [] + curr_color = 0 if is_safe_attribute_table(misp_event, "Galaxy"): # There is some galaxies for this object - for item in getattr(misp_event, "Galaxy"): - # flowable_table.append([get_unoverflowable_paragraph(item["name"],col2_style)]) - flowable_table.append(self.create_flowable_table_from_one_galaxy(item)) - i += 1 - answer_tags = create_flowable_table_from_data(flowable_table, ["99%"]) + + for curr_galaxy in getattr(misp_event, "Galaxy"): + # For each galaxy of the misp object + + # Add metadata about the Galaxy + galaxy_metadata, nb_added_item = self.create_flowable_table_from_one_galaxy(curr_galaxy) + flowable_table += galaxy_metadata + + # Construct the line color scheme and line scheme + scheme_alternation += [curr_color] * nb_added_item + + # Add metadata about clusters + curr_cluster = Galaxy_cluster(self.config, self.value_formatter) + clusters_metadata, nb_added_item = curr_cluster.create_flowable_table_from_galaxy_clusters(curr_galaxy) + flowable_table += clusters_metadata + + # Construct the line color scheme and line scheme + scheme_alternation += [curr_color] * nb_added_item + curr_color += 1 if curr_color == 0 else 0 + + # Apply the scheme + answer_tags = create_flowable_table_from_data(flowable_table, color_alternation=scheme_alternation , line_alternation=scheme_alternation) else: # No galaxies for this object - answer_tags = [Paragraph("No galaxies", col2_style)] + answer_tags = [self.value_formatter.get_unoverflowable_paragraph("No galaxies")] return answer_tags - def create_flowable_table_from_one_galaxy(self, misp_galaxy): ''' Returns a table (flowable) representing the galaxy @@ -1215,7 +1285,7 @@ class Galaxy(): :return: a table representing this misp's galaxy's attributes, to add to the pdf as a flowable ''' data = [] - col1_style, col2_style = get_table_styles() + nb_added_item = 0 # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, # to_display_if_not_present) are store in the following list @@ -1230,15 +1300,11 @@ class Galaxy(): # The attribute exists, we fetch it and create the row data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_unoverflowable_paragraph(misp_galaxy[item[1]])]) + nb_added_item += 1 - # Clusters - item = ["Clusters", 'GalaxyCluster', "None"] - # data.append([Paragraph(item[0], col1_style), create_flowable_table_from_galaxy_clusters(misp_galaxy)]) - - tmp_table = Table(data, ["25%", "75%"]) # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) - return [tmp_table] + return data, nb_added_item class Galaxy_cluster(): @@ -1249,40 +1315,44 @@ class Galaxy_cluster(): self.value_formatter = value_formatter # ---------------------------------------------------------------------- - def create_flowable_table_from_galaxy_clusters(self, misp_event): + def create_flowable_table_from_galaxy_clusters(self, misp_galaxy): ''' Returns a Table (flowable) to add to a pdf, representing the list of galaxy clusters of a galaxy :param misp_event: A misp event :return: a table of flowables to add to the pdf ''' - flowable_table = [] - i = 0 + data = [] + nb_added_item = 0 + + if is_safe_dict_attribute(misp_galaxy, "GalaxyCluster"): + # There is some clusters for this object + for curr_cluster in misp_galaxy["GalaxyCluster"]: + + # For each cluster + tmp_data, curr_added_items = self.create_flowable_table_from_one_galaxy_cluster(curr_cluster) + data += tmp_data + nb_added_item += curr_added_items - if is_safe_dict_attribute(misp_event, "GalaxyCluster"): - # There is some galaxies for this object - for item in misp_event["GalaxyCluster"]: - # flowable_table.append([get_unoverflowable_paragraph(item["name"],col2_style)]) - flowable_table.append(self.create_flowable_table_from_one_galaxy_cluster(item)) - i += 1 - answer_tags = create_flowable_table_from_data(flowable_table, ["99%"]) else: # No galaxies for this object - answer_tags = [self.value_formatter.get_unoverflowable_paragraph("No galaxy cluster")] + data = [self.value_formatter.get_unoverflowable_paragraph("No galaxy cluster")] + nb_added_item += 1 - return answer_tags + return data, nb_added_item def create_flowable_table_from_one_galaxy_cluster(self, misp_cluster): ''' - Returns a table (flowable) representing the galaxy + Returns a table (flowable) representing a galaxy cluster :param misp_attribute: A misp galaxy - :return: a table representing this misp's galaxy's attributes, to add to the pdf as a flowable + :return: a table representing this misp's galaxy's cluster attributes, to add to the pdf as a flowable ''' data = [] + nb_added_item = 0 # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, # to_display_if_not_present) are store in the following list - list_attr_automated = [["Name", 'name', "None"], + list_attr_automated = [["Cluster #", 'tag_name', "None"], ["Type", 'type', "None"], ["Description", 'description', "None"], ["NameSpace", 'namespace', "None"]] @@ -1293,14 +1363,13 @@ class Galaxy_cluster(): # The attribute exists, we fetch it and create the row data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_unoverflowable_paragraph(misp_cluster[item[1]])]) + nb_added_item += 1 - tmp_table = Table(data, ["25%", "75%"]) - print(tmp_table) + # tmp_table = Table(data, ["25%", "75%"]) + # print(tmp_table) # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) - return [tmp_table] - - + return data, nb_added_item ######################################################################## @@ -1322,8 +1391,7 @@ class Statics_Drawings(): # TODO : add_header() # TODO : add_footer() - - def add_metadata(self,canvas, doc): + def add_metadata(self, canvas, doc): ''' Allow to add metadata to the pdf. Would need deeper digging to change other metadata. :param canvas: / Automatically filled during pdf compilation @@ -1342,7 +1410,7 @@ class Statics_Drawings(): if is_safe_attribute(getattr(self.misp_event, 'Orgc'), 'name'): canvas.setAuthor(getattr(getattr(self.misp_event, 'Orgc'), 'name')) - if is_in_config(self.config,1) : + if is_in_config(self.config, 1): canvas.setCreator(self.config[moduleconfig[1]]) else: canvas.setCreator(getattr(getattr(self.misp_event, 'Orgc'), 'name')) @@ -1350,8 +1418,7 @@ class Statics_Drawings(): if is_safe_attribute(self.misp_event, 'uuid'): canvas.setKeywords(getattr(self.misp_event, 'uuid')) - - def add_page_number(self,canvas, doc): + def add_page_number(self, canvas, doc): ''' Draw the page number on each page :param canvas: / Automatically filled during pdf compilation @@ -1401,7 +1468,7 @@ def collect_parts(misp_event, config=None): curr_attr = Attributes(config, curr_val_f) curr_object = Object(config, curr_val_f) - if is_in_config(config,2) : # If description is activated + if is_in_config(config, 2): # If description is activated description = Paragraph("Description", sample_style_sheet['Heading2']) description_text = curr_event.create_flowable_description_from_event(misp_event) flowables.append(description) @@ -1410,7 +1477,7 @@ def collect_parts(misp_event, config=None): subtitle = Paragraph("General information", sample_style_sheet['Heading2']) table_general_metainformation = curr_event.create_flowable_table_from_event(misp_event) flowables.append(subtitle) - flowables.append(table_general_metainformation) + flowables += table_general_metainformation flowables.append(PageBreak()) @@ -1449,7 +1516,8 @@ def export_flowables_to_pdf(document, misp_event, flowables, config): onFirstPage=static_drawer.set_template, # Pagination for first page onLaterPages=static_drawer.set_template, # Pagination for all other page ) - # Old way : onLaterPages=partial(static_drawer.set_template, misp_event=misp_event), # Pagination for all other page + # Old way : onLaterPages=partial(static_drawer.set_template, misp_event=misp_event), # Pagination for all other page + ######################################################################## # "EXTERNAL" exposed METHODS. Meant to be used outside of this class. @@ -1484,7 +1552,6 @@ def convert_event_in_pdf_buffer(misp_event, config=None): return pdf_value - def get_values_from_buffer(pdf_buffer): return pdf_buffer.value() From 24e1a1732c3dedd65d7bca31730b49d40bc7cbe0 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Thu, 28 Feb 2019 16:34:07 +0100 Subject: [PATCH 04/10] fix: [reportlab] Clusters added. Still UX to perform --- pymisp/tools/reportlab_generator.py | 163 ++++++++++++++++++---------- 1 file changed, 107 insertions(+), 56 deletions(-) diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index cd418e9..74f92ef 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -716,6 +716,34 @@ class Value_Formatter(): return answer + def get_galaxy_name_value(self, misp_galaxy): + item = ["Name", 'name', "None", "namespace", "type"] + if is_safe_dict_attribute(misp_galaxy, item[1]): + return self.get_unoverflowable_paragraph(safe_string(misp_galaxy[item[1]]) + + " from " + safe_string(misp_galaxy[item[3]]) + ":" + + safe_string(misp_galaxy[item[4]]), do_escape_string=False) + return self.get_unoverflowable_paragraph(item[2]) + + def get_galaxy_cluster_name_value(self, misp_cluster): + item = ["Name", 'value', "None", "source", "meta", "synonyms"] + tmp_text = "" + + if is_safe_dict_attribute(misp_cluster, item[1]): + print(misp_cluster[item[1]]) + tmp_text += safe_string(misp_cluster[item[1]]) + + #if is_safe_dict_attribute(misp_cluster, item[3]) : + # tmp_text += "
Source : " + 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 += "
Synonyms : " + 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) + return self.get_unoverflowable_paragraph(item[2]) class Event_Metadata(): @@ -801,11 +829,7 @@ class Event_Metadata(): # Galaxies item = ["Related Galaxies", 'Galaxy', "None"] curr_Galaxy = Galaxy(self.config, self.value_formatter) - 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']) - - flowable_table.append(galaxy_title) - flowable_table.append(curr_Galaxy.get_galaxy_value(misp_event, item)) + flowable_table += curr_Galaxy.get_galaxy_value(misp_event, item) return flowable_table @@ -1013,11 +1037,7 @@ class Attributes(): # Galaxies item = ["Related Galaxies", 'Galaxy', "None"] curr_Galaxy = Galaxy(self.config, self.value_formatter) - if is_safe_attribute_table(misp_attribute, item[1]) and is_in_config(self.config, 3): - galaxy_title = Paragraph(item[0], self.sample_style_sheet['Heading5']) - - flowable_table.append(galaxy_title) - flowable_table.append(curr_Galaxy.get_galaxy_value(misp_attribute, item)) + flowable_table += curr_Galaxy.get_galaxy_value(misp_attribute, item) return flowable_table @@ -1219,6 +1239,7 @@ class Galaxy(): def __init__(self, config, value_formatter): self.config = config self.value_formatter = value_formatter + self.sample_style_sheet = getSampleStyleSheet() # ---------------------------------------------------------------------- @@ -1232,9 +1253,20 @@ class Galaxy(): :param col2_style: style to be applied on the returned paragraph :return: a Flowable to add in the pdf, regarding the values of "galaxies" ''' - if is_safe_attribute_table(misp_event, item[1]): - return self.create_flowable_table_from_galaxies(misp_event) - return self.value_formatter.get_unoverflowable_paragraph(item[2]) + + flowable_table = [] + + # 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']) + + flowable_table.append(galaxy_title) + flowable_table += self.create_flowable_table_from_galaxies(misp_event) + else : + flowable_table.append(self.value_formatter.get_unoverflowable_paragraph(item[2])) + + return flowable_table def create_flowable_table_from_galaxies(self, misp_event): @@ -1247,6 +1279,7 @@ class Galaxy(): flowable_table = [] scheme_alternation = [] curr_color = 0 + i = 0 if is_safe_attribute_table(misp_event, "Galaxy"): # There is some galaxies for this object @@ -1254,6 +1287,10 @@ class Galaxy(): for curr_galaxy in getattr(misp_event, "Galaxy"): # For each galaxy of the misp object + galaxy_title = Paragraph("Galaxy # " + str(i), self.sample_style_sheet['Heading6']) + flowable_table.append(galaxy_title) + i += 1 + # Add metadata about the Galaxy galaxy_metadata, nb_added_item = self.create_flowable_table_from_one_galaxy(curr_galaxy) flowable_table += galaxy_metadata @@ -1261,22 +1298,27 @@ class Galaxy(): # Construct the line color scheme and line scheme scheme_alternation += [curr_color] * nb_added_item + # Apply the scheme + # answer_tags = create_flowable_table_from_data(flowable_table) + # Add metadata about clusters curr_cluster = Galaxy_cluster(self.config, self.value_formatter) - clusters_metadata, nb_added_item = curr_cluster.create_flowable_table_from_galaxy_clusters(curr_galaxy) + clusters_metadata = curr_cluster.create_flowable_table_from_galaxy_clusters(curr_galaxy) flowable_table += clusters_metadata + + ''' # Construct the line color scheme and line scheme scheme_alternation += [curr_color] * nb_added_item curr_color += 1 if curr_color == 0 else 0 + ''' - # Apply the scheme - answer_tags = create_flowable_table_from_data(flowable_table, color_alternation=scheme_alternation , line_alternation=scheme_alternation) else: # No galaxies for this object answer_tags = [self.value_formatter.get_unoverflowable_paragraph("No galaxies")] + flowable_table.append(create_flowable_table_from_data(answer_tags)) - return answer_tags + return flowable_table def create_flowable_table_from_one_galaxy(self, misp_galaxy): ''' @@ -1287,24 +1329,24 @@ class Galaxy(): data = [] nb_added_item = 0 - # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, - # to_display_if_not_present) are store in the following list - list_attr_automated = [["Name", 'name', "None"], - ["Type", 'type', "None"], - ["Description", 'description', "None"], - ["NameSpace", 'namespace', "None"]] + # Name + item = ["Name", 'name', "None"] + if is_safe_dict_attribute(misp_galaxy, item[1]): + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_galaxy_name_value(misp_galaxy)]) + nb_added_item += 1 - # Automated adding of standard (python) attributes of the misp object - for item in list_attr_automated: - if is_safe_dict_attribute(misp_galaxy, item[1]): - # The attribute exists, we fetch it and create the row - data.append([self.value_formatter.get_col1_paragraph(item[0]), + # Description + item = ["Description", 'description', "None"] + if is_safe_dict_attribute(misp_galaxy, item[1]): + data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_unoverflowable_paragraph(misp_galaxy[item[1]])]) - nb_added_item += 1 + nb_added_item += 1 - # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : - # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) - return data, nb_added_item + flowable_table = [] + flowable_table.append(create_flowable_table_from_data(data)) + + return flowable_table, nb_added_item class Galaxy_cluster(): @@ -1313,6 +1355,7 @@ class Galaxy_cluster(): def __init__(self, config, value_formatter): self.config = config self.value_formatter = value_formatter + self.sample_style_sheet = getSampleStyleSheet() # ---------------------------------------------------------------------- def create_flowable_table_from_galaxy_clusters(self, misp_galaxy): @@ -1323,23 +1366,39 @@ class Galaxy_cluster(): ''' data = [] - nb_added_item = 0 + i = 0 + + item = ["Cluster #", 'name', "None"] + if is_safe_dict_attribute(misp_galaxy, "GalaxyCluster"): # There is some clusters for this object for curr_cluster in misp_galaxy["GalaxyCluster"]: + ''' + galaxy_title = [Paragraph("Cluster #" + str(i), self.sample_style_sheet['Heading6'])] + data.append(galaxy_title) + i += 1 + ''' + # For each cluster - tmp_data, curr_added_items = self.create_flowable_table_from_one_galaxy_cluster(curr_cluster) - data += tmp_data - nb_added_item += curr_added_items + 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, color_alternation = [0], line_alternation=[0])) + data.append([self.value_formatter.get_col1_paragraph(item[0]), tmp_flowable_table]) + + # data += tmp_data else: # No galaxies for this object data = [self.value_formatter.get_unoverflowable_paragraph("No galaxy cluster")] - nb_added_item += 1 - return data, nb_added_item + + flowable_table = [] + flowable_table.append(create_flowable_table_from_data(data)) + + + return flowable_table def create_flowable_table_from_one_galaxy_cluster(self, misp_cluster): ''' @@ -1348,28 +1407,20 @@ class Galaxy_cluster(): :return: a table representing this misp's galaxy's cluster attributes, to add to the pdf as a flowable ''' data = [] - nb_added_item = 0 - # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, - # to_display_if_not_present) are store in the following list - list_attr_automated = [["Cluster #", 'tag_name', "None"], - ["Type", 'type', "None"], - ["Description", 'description', "None"], - ["NameSpace", 'namespace', "None"]] + # Name + item = ["Name", 'name', "None"] + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_galaxy_cluster_name_value(misp_cluster)]) - # Automated adding of standard (python) attributes of the misp object - for item in list_attr_automated: - if is_safe_dict_attribute(misp_cluster, item[1]): - # The attribute exists, we fetch it and create the row - data.append([self.value_formatter.get_col1_paragraph(item[0]), + # Description + item = ["Description", 'description', "None"] + data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_unoverflowable_paragraph(misp_cluster[item[1]])]) - nb_added_item += 1 - # tmp_table = Table(data, ["25%", "75%"]) - # print(tmp_table) - # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : - # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) - return data, nb_added_item + # Refs ? + + return data ######################################################################## From a06c8cf5b8574f120d4a89a5f535e12430d3d253 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Fri, 1 Mar 2019 09:06:01 +0100 Subject: [PATCH 05/10] fix: [reportlab] Galaxies and Clusters printing --- Pipfile | 3 +- Pipfile.lock | 218 ++++++++++++------ README.md | 2 + docs/tutorial/Usage-NG.ipynb | 38 +-- pymisp/__init__.py | 12 +- pymisp/api.py | 71 +++--- pymisp/data/misp-objects | 2 +- pymisp/tools/reportlab_generator.py | 158 ++++++++----- setup.py | 5 +- .../event_obj_attr_tag.json | 2 +- .../event_obj_def_param.json | 4 +- tests/reportlab_testfiles/image_event.json | 22 +- .../reportlab_testfiles/mainly_objects_1.json | 14 +- .../reportlab_testfiles/mainly_objects_2.json | 10 +- tests/test_reportlab.py | 10 +- tests/testlive_comprehensive.py | 2 +- 16 files changed, 356 insertions(+), 217 deletions(-) diff --git a/Pipfile b/Pipfile index a6b48c8..1a384db 100644 --- a/Pipfile +++ b/Pipfile @@ -10,10 +10,9 @@ codecov = "*" requests-mock = "*" [packages] -pymisp = {editable = true,extras = ["fileobjects", "neo", "openioc", "virustotal"],path = "."} +pymisp = {editable = true,extras = ["fileobjects", "neo", "openioc", "virustotal", "pdfexport"],path = "."} pydeep = {editable = true,git = "https://github.com/kbandla/pydeep.git"} pymispwarninglists = {editable = true,git = "https://github.com/MISP/PyMISPWarningLists.git"} -reportlab = "*" [requires] python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index 1258507..bde1f40 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "2551d32f7430eba34eac975cc1b28eca13fe9faff7197d83f312d7de8df187da" + "sha256": "c95b6920af9d48d6e38e0456394f752479064c9f3091cf3e6b93e751de21cfad" }, "pipfile-spec": 6, "requires": { @@ -23,6 +23,13 @@ ], "version": "==0.24.0" }, + "attrs": { + "hashes": [ + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + ], + "version": "==18.2.0" + }, "beautifulsoup4": { "hashes": [ "sha256:034740f6cb549b4e932ae1ab975581e6103ac8f942200a0e9759065984391858", @@ -40,40 +47,36 @@ }, "cffi": { "hashes": [ - "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", - "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", - "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", - "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", - "sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30", - "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", - "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", - "sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b", - "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", - "sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e", - "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", - "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", - "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", - "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", - "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", - "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", - "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", - "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", - "sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5", - "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", - "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", - "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", - "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", - "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", - "sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2", - "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", - "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", - "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", - "sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", - "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", - "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", - "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb" + "sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f", + "sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11", + "sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d", + "sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891", + "sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf", + "sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c", + "sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed", + "sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b", + "sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a", + "sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585", + "sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea", + "sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f", + "sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33", + "sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145", + "sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a", + "sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3", + "sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f", + "sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd", + "sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804", + "sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d", + "sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92", + "sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f", + "sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84", + "sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb", + "sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7", + "sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7", + "sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35", + "sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889" ], - "version": "==1.11.5" + "version": "==1.12.2" }, "chardet": { "hashes": [ @@ -98,27 +101,27 @@ }, "cryptography": { "hashes": [ - "sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af", - "sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e", - "sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2", - "sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7", - "sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079", - "sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063", - "sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401", - "sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695", - "sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85", - "sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3", - "sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad", - "sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca", - "sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd", - "sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f", - "sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159", - "sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0", - "sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e", - "sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3", - "sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00" + "sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1", + "sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705", + "sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6", + "sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1", + "sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8", + "sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151", + "sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d", + "sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659", + "sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537", + "sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e", + "sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb", + "sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c", + "sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9", + "sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5", + "sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad", + "sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a", + "sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460", + "sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd", + "sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6" ], - "version": "==2.5" + "version": "==2.6.1" }, "decorator": { "hashes": [ @@ -143,10 +146,10 @@ }, "jsonschema": { "hashes": [ - "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", - "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02" + "sha256:acc8a90c31d11060516cfd0b414b9f8bcf4bc691b21f0f786ea57dd5255c79db", + "sha256:dd3f8ecb1b52d94d45eedb67cb86cac57b94ded562c5d98f63719e55ce58557b" ], - "version": "==2.6.0" + "version": "==3.0.0" }, "lief": { "hashes": [ @@ -156,9 +159,9 @@ }, "neobolt": { "hashes": [ - "sha256:f70df7422568f3f92f065482237dabe3b96cd49a921c5e17feb1c9e68fdd0357" + "sha256:3324f2b319e84acb82e37a81ef75f3f7ce71c149387daf900589377db48bed2a" ], - "version": "==1.7.3" + "version": "==1.7.4" }, "neotime": { "hashes": [ @@ -166,13 +169,48 @@ ], "version": "==1.7.4" }, + "pillow": { + "hashes": [ + "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", + "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", + "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", + "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", + "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", + "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", + "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", + "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", + "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", + "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", + "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", + "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", + "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", + "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", + "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", + "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", + "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", + "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", + "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", + "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", + "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", + "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", + "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", + "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", + "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", + "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", + "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", + "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", + "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", + "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e" + ], + "version": "==5.4.1" + }, "prompt-toolkit": { "hashes": [ - "sha256:88002cc618cacfda8760c4539e76c3b3f148ecdb7035a3d422c7ecdc90c2a3ba", - "sha256:c6655a12e9b08edb8cf5aeab4815fd1e1bdea4ad73d3bbf269cf2e0c4eb75d5e", - "sha256:df5835fb8f417aa55e5cafadbaeb0cf630a1e824aad16989f9f0493e679ec010" + "sha256:11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780", + "sha256:2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1", + "sha256:977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55" ], - "version": "==2.0.8" + "version": "==2.0.9" }, "py2neo": { "hashes": [ @@ -204,7 +242,8 @@ "fileobjects", "neo", "openioc", - "virustotal" + "virustotal", + "pdfexport" ], "path": "." }, @@ -220,6 +259,12 @@ ], "version": "==19.0.0" }, + "pyrsistent": { + "hashes": [ + "sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2" + ], + "version": "==0.14.11" + }, "python-dateutil": { "hashes": [ "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", @@ -241,6 +286,39 @@ ], "version": "==2018.9" }, + "reportlab": { + "hashes": [ + "sha256:069f684cd0aaa518a27dc9124aed29cee8998e21ddf19604e53214ec8462bdd7", + "sha256:09b68ec01d86b4b120456b3f3202570ec96f57624e3a4fc36f3829323391daa4", + "sha256:0c32be9a406172c29ea20ff55a709ccac1e7fb09f15aba67cb7b455fd1d3dbe0", + "sha256:233196cf25e97cfe7c452524ea29d9a4909f1cb66599299233be1efaaaa7a7a3", + "sha256:2b5e4533f3e5b962835a5ce44467e66d1ecc822761d1b508077b5087a06be338", + "sha256:2e860bcdace5a558356802a92ae8658d7e5fdaa00ded82e83a3f2987c562cb66", + "sha256:3546029e63a9a9dc24ee38959eb417678c2425b96cd27b31e09e216dafc94666", + "sha256:4452b93f9c73b6b70311e7d69082d64da81b38e91bfb4766397630092e6da6fd", + "sha256:528c74a1c6527d1859c2c7a64a94a1cba485b00175162ea23699ae58a1e94939", + "sha256:6116e750f98018febc08dfee6df20446cf954adbcfa378d2c703d56c8864aff3", + "sha256:6b2b3580c647d75ef129172cb3da648cdb24566987b0b59c5ebb80ab770748d6", + "sha256:727b5f2bed08552d143fc99649b1863c773729f580a416844f9d9967bb0a1ae8", + "sha256:74c24a3ec0a3d4f8acb13a07192f45bdb54a1cc3c2286241677e7e8bcd5011fa", + "sha256:98ccd2f8b4f8636db05f3f14db0b471ad6bb4b66ae0dc9052c4822b3bd5d6a7d", + "sha256:a5905aa567946bc938b489a7249c7890c3fd3c9b7b5680dece5bc551c2ddbe0d", + "sha256:acbb7f676b8586b770719e9683eda951fdb38eb7970d46fcbf3cdda88d912a64", + "sha256:b5e30f865add48cf880f1c363eb505b97f2f7baaa88c155f87a335a76515a3e5", + "sha256:be2a7c33a2c28bbd3f453ffe4f0e5200b88c803a097f4cf52d69c6b53fad7a8f", + "sha256:c356bb600f59ac64955813d6497a08bfd5d0c451cb5829b61e3913d0ac084e26", + "sha256:c7ec4ae2393beab584921b1287a04e94fd98c28315e348362d89b85f4b464546", + "sha256:d476edc831bb3e9ebd04d1403abaf3ea57b3e4c2276c91a54fdfb6efbd3f9d97", + "sha256:db059e1a0691c872784062421ec51848539eb4f5210142682e61059a5ca7cc55", + "sha256:dd423a6753509ab14a0ac1b5be39d219c8f8d3781cce3deb4f45eda31969b5e8", + "sha256:ed9b7c0d71ce6fe2b31c6cde530ad8238632b876a5d599218739bda142a77f7c", + "sha256:f0a2465af4006f97b05e1f1546d67d3a3213d414894bf28be7f87f550a7f4a55", + "sha256:f20bfe26e57e8e1f575a9e0325be04dd3562db9f247ffdd73b5d4df6dec53bc2", + "sha256:f3463f2cb40a1b515ac0133ba859eca58f53b56760da9abb27ed684c565f853c", + "sha256:facc3c9748ab1525fb8401a1223bce4f24f0d6aa1a9db86c55db75777ccf40f9" + ], + "version": "==3.5.13" + }, "requests": { "hashes": [ "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", @@ -257,10 +335,10 @@ }, "soupsieve": { "hashes": [ - "sha256:466910df7561796a60748826781ebe9a888f7a1668a636ae86783f44d10aae73", - "sha256:87db12ae79194f0ff9808d2b1641c4f031ae39ffa3cab6b907ea7c1e5e5ed445" + "sha256:afa56bf14907bb09403e5d15fbed6275caa4174d36b975226e3b67a3bb6e2c4b", + "sha256:eaed742b48b1f3e2d45ba6f79401b2ed5dc33b2123dfe216adb90d4bfa0ade26" ], - "version": "==1.7.3" + "version": "==1.8" }, "urllib3": { "extras": [ @@ -347,11 +425,11 @@ }, "coveralls": { "hashes": [ - "sha256:ab638e88d38916a6cedbf80a9cd8992d5fa55c77ab755e262e00b36792b7cd6d", - "sha256:b2388747e2529fa4c669fb1e3e2756e4e07b6ee56c7d9fce05f35ccccc913aa0" + "sha256:6f213e461390973f4a97fb9e9d4ebd4956af296ff0a4d868e622108145835cb7", + "sha256:a7d0078c9e9b5692c03dcd3884647e837836c265c01e98094632feadef767d36" ], "index": "pypi", - "version": "==1.5.1" + "version": "==1.6.0" }, "docopt": { "hashes": [ diff --git a/README.md b/README.md index 68a087d..b598d84 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ README [![Documentation Status](https://readthedocs.org/projects/pymisp/badge/?version=latest)](http://pymisp.readthedocs.io/?badge=latest) [![Build Status](https://travis-ci.org/MISP/PyMISP.svg?branch=master)](https://travis-ci.org/MISP/PyMISP) [![Coverage Status](https://coveralls.io/repos/github/MISP/PyMISP/badge.svg?branch=master)](https://coveralls.io/github/MISP/PyMISP?branch=master) +[![Python 3.6](https://img.shields.io/badge/python-3.6+-blue.svg)](https://www.python.org/downloads/release/python-360/) +[![PyPi version](https://img.shields.io/pypi/v/pymisp.svg)](https://pypi.python.org/pypi/pymisp/) # PyMISP - Python Library to access MISP diff --git a/docs/tutorial/Usage-NG.ipynb b/docs/tutorial/Usage-NG.ipynb index a0479c6..c3a4eac 100644 --- a/docs/tutorial/Usage-NG.ipynb +++ b/docs/tutorial/Usage-NG.ipynb @@ -23,7 +23,7 @@ "misp_url = 'http://127.0.0.1:8080'\n", "# Can be found in the MISP web interface under \n", "# http://+MISP_URL+/users/view/me -> Authkey\n", - "misp_key = 'LBelWqKY9SQyG0huZzAMqiEBl6FODxpgRRXMsZFu'\n", + "misp_key = 'aJAmQQoBhVL5jqUDSucIkPrEYIbFyW0wwQnxyBfc'\n", "# Should PyMISP verify the MISP certificate\n", "misp_verifycert = False" ] @@ -87,10 +87,11 @@ }, "outputs": [], "source": [ - "response = misp.search(publish_timestamp='2h')\n", + "response = misp.search(publish_timestamp='2d')\n", "\n", + "print (response)\n", "events = []\n", - "for event in response['response']:\n", + "for event in response:\n", " me = MISPEvent()\n", " me.load(event)\n", " events.append(me)\n", @@ -133,7 +134,7 @@ "response = misp.search(timestamp=ts-36000)\n", "\n", "events = []\n", - "for event in response['response']:\n", + "for event in response:\n", " me = MISPEvent()\n", " me.load(event)\n", " events.append(me)\n", @@ -179,10 +180,10 @@ "metadata": {}, "outputs": [], "source": [ - "response = misp.search(controller='attributes', publish_timestamp='1h')\n", + "response = misp.search(controller='attributes', publish_timestamp='1d')\n", "\n", "attributes = []\n", - "for attribute in response['response']['Attribute']:\n", + "for attribute in response['Attribute']:\n", " ma = MISPAttribute()\n", " ma.from_dict(**attribute)\n", " attributes.append(ma)\n", @@ -197,10 +198,10 @@ "metadata": {}, "outputs": [], "source": [ - "response = misp.search(controller='attributes', publish_timestamp=['2h', '1h'])\n", + "response = misp.search(controller='attributes', publish_timestamp=['2d', '1h'])\n", "\n", "attributes = []\n", - "for attribute in response['response']['Attribute']:\n", + "for attribute in response['Attribute']:\n", " ma = MISPAttribute()\n", " ma.from_dict(**attribute)\n", " attributes.append(ma)\n", @@ -229,7 +230,7 @@ "response = misp.search(controller='attributes', timestamp=ts - 36000)\n", "\n", "attributes = []\n", - "for attribute in response['response']['Attribute']:\n", + "for attribute in response['Attribute']:\n", " ma = MISPAttribute()\n", " ma.from_dict(**attribute)\n", " attributes.append(ma)\n", @@ -256,7 +257,7 @@ "response = misp.search_index(eventinfo='Cobalt Strike')\n", "\n", "events = []\n", - "for event in response['response']:\n", + "for event in response:\n", " me = MISPEvent()\n", " me.from_dict(**event)\n", " events.append(me)\n", @@ -280,10 +281,10 @@ "metadata": {}, "outputs": [], "source": [ - "response = misp.search_index(tag='malware_classification:malware-category=\"Ransomware\"')\n", + "response = misp.search_index(tags='malware_classification:malware-category=\"Ransomware\"')\n", "\n", "events = []\n", - "for event in response['response']:\n", + "for event in response:\n", " me = MISPEvent()\n", " me.from_dict(**event)\n", " events.append(me)\n", @@ -303,7 +304,7 @@ "response = misp.search_index(timestamp='1h')\n", "\n", "events = []\n", - "for event in response['response']:\n", + "for event in response:\n", " me = MISPEvent()\n", " me.from_dict(**event)\n", " events.append(me)\n", @@ -328,8 +329,9 @@ "outputs": [], "source": [ "event = MISPEvent()\n", - "event.load(misp.get(events[0].id))\n", - "print(event.to_json())" + "#event.load(misp.get(events[0].id))\n", + "print (misp.get(events[0].id))\n", + "#print(event.to_json())" ] }, { @@ -345,7 +347,7 @@ "metadata": {}, "outputs": [], "source": [ - "complex_query = misp.build_complex_query(or_parameters=['59.157.4.2', 'hotfixmsupload.com'])\n", + "complex_query = misp.build_complex_query(or_parameters=['59.157.4.2', 'hotfixmsupload.com', '8.8.8.8'])\n", "events = misp.search(value=complex_query, pythonify=True)\n", "\n", "for e in events:\n", @@ -365,7 +367,7 @@ "metadata": {}, "outputs": [], "source": [ - "misp.sighting(value=e.attributes[3].value)" + "misp.sighting(value=e.attributes[1].value)" ] }, { @@ -374,7 +376,7 @@ "metadata": {}, "outputs": [], "source": [ - "misp.sighting_list(e.attributes[3].id)" + "misp.sighting_list(e.attributes[1].id)" ] }, { diff --git a/pymisp/__init__.py b/pymisp/__init__.py index b4b7264..512de05 100644 --- a/pymisp/__init__.py +++ b/pymisp/__init__.py @@ -42,7 +42,17 @@ try: from .tools import openioc # noqa from .tools import load_warninglists # noqa from .tools import ext_lookups # noqa - from .tools import reportlab_generator # noqa + + if sys.version_info >= (3, 4): + # Let's not bother with python 2 + try: + from .tools import reportlab_generator # noqa + except ImportError: + # FIXME: The import should not raise an exception if reportlab isn't installed + pass + except NameError: + # FIXME: The import should not raise an exception if reportlab isn't installed + pass if sys.version_info >= (3, 6): from .aping import ExpandedPyMISP # noqa logger.debug('pymisp loaded properly') diff --git a/pymisp/api.py b/pymisp/api.py index fbc4b80..2ae60fa 100644 --- a/pymisp/api.py +++ b/pymisp/api.py @@ -69,9 +69,10 @@ class PyMISP(object): :param proxies: Proxy dict as describes here: http://docs.python-requests.org/en/master/user/advanced/#proxies :param cert: Client certificate, as described there: http://docs.python-requests.org/en/master/user/advanced/#client-side-certificates :param asynch: Use asynchronous processing where possible + :param auth: The auth parameter is passed directly to requests, as described here: http://docs.python-requests.org/en/master/user/authentication/ """ - def __init__(self, url, key, ssl=True, out_type='json', debug=None, proxies=None, cert=None, asynch=False): + def __init__(self, url, key, ssl=True, out_type='json', debug=None, proxies=None, cert=None, asynch=False, auth=None): if not url: raise NoURL('Please provide the URL of your MISP instance.') if not key: @@ -83,6 +84,7 @@ class PyMISP(object): self.proxies = proxies self.cert = cert self.asynch = asynch + self.auth = auth if asynch and not ASYNC_OK: logger.critical("You turned on Async, but don't have requests_futures installed") self.asynch = False @@ -169,6 +171,7 @@ class PyMISP(object): else: local_session = requests.Session with local_session() as s: + req.auth = self.auth prepped = s.prepare_request(req) prepped.headers.update( {'Authorization': self.key, @@ -1779,7 +1782,7 @@ class PyMISP(object): def get_roles_list(self): """Get the list of existing roles""" - url = urljoin(self.root_url, '/roles') + url = urljoin(self.root_url, 'roles') response = self._prepare_request('GET', url) return self._check_response(response) @@ -1787,13 +1790,13 @@ class PyMISP(object): def get_tags_list(self): """Get the list of existing tags.""" - url = urljoin(self.root_url, '/tags') + url = urljoin(self.root_url, 'tags') response = self._prepare_request('GET', url) return self._check_response(response)['Tag'] def get_tag(self, tag_id): """Get a tag by id.""" - url = urljoin(self.root_url, '/tags/view/{}'.format(tag_id)) + url = urljoin(self.root_url, 'tags/view/{}'.format(tag_id)) response = self._prepare_request('GET', url) return self._check_response(response) @@ -1827,7 +1830,7 @@ class PyMISP(object): old_tag = self.get_tag(tag_id) new_tag = self._set_tag_parameters(name, colour, exportable, hide_tag, org_id, count, user_id, numerical_value, attribute_count, old_tag) - url = urljoin(self.root_url, '/tags/edit/{}'.format(tag_id)) + url = urljoin(self.root_url, 'tags/edit/{}'.format(tag_id)) response = self._prepare_request('POST', url, json.dumps(new_tag)) return self._check_response(response) @@ -1835,7 +1838,7 @@ class PyMISP(object): """Edit the tag using a json file.""" with open(json_file, 'rb') as f: jdata = json.load(f) - url = urljoin(self.root_url, '/tags/edit/{}'.format(tag_id)) + url = urljoin(self.root_url, 'tags/edit/{}'.format(tag_id)) response = self._prepare_request('POST', url, json.dumps(jdata)) return self._check_response(response) @@ -1853,38 +1856,38 @@ class PyMISP(object): def get_taxonomies_list(self): """Get all the taxonomies.""" - url = urljoin(self.root_url, '/taxonomies') + url = urljoin(self.root_url, 'taxonomies') response = self._prepare_request('GET', url) return self._check_response(response) def get_taxonomy(self, taxonomy_id): """Get a taxonomy by id.""" - url = urljoin(self.root_url, '/taxonomies/view/{}'.format(taxonomy_id)) + url = urljoin(self.root_url, 'taxonomies/view/{}'.format(taxonomy_id)) response = self._prepare_request('GET', url) return self._check_response(response) def update_taxonomies(self): """Update all the taxonomies.""" - url = urljoin(self.root_url, '/taxonomies/update') + url = urljoin(self.root_url, 'taxonomies/update') response = self._prepare_request('POST', url) return self._check_response(response) def enable_taxonomy(self, taxonomy_id): """Enable a taxonomy by id.""" - url = urljoin(self.root_url, '/taxonomies/enable/{}'.format(taxonomy_id)) + url = urljoin(self.root_url, 'taxonomies/enable/{}'.format(taxonomy_id)) response = self._prepare_request('POST', url) return self._check_response(response) def disable_taxonomy(self, taxonomy_id): """Disable a taxonomy by id.""" self.disable_taxonomy_tags(taxonomy_id) - url = urljoin(self.root_url, '/taxonomies/disable/{}'.format(taxonomy_id)) + url = urljoin(self.root_url, 'taxonomies/disable/{}'.format(taxonomy_id)) response = self._prepare_request('POST', url) return self._check_response(response) def get_taxonomy_tags_list(self, taxonomy_id): """Get all the tags of a taxonomy by id.""" - url = urljoin(self.root_url, '/taxonomies/view/{}'.format(taxonomy_id)) + url = urljoin(self.root_url, 'taxonomies/view/{}'.format(taxonomy_id)) response = self._prepare_request('GET', url) return self._check_response(response)["entries"] @@ -1892,13 +1895,13 @@ class PyMISP(object): """Enable all the tags of a taxonomy by id.""" enabled = self.get_taxonomy(taxonomy_id)['Taxonomy']['enabled'] if enabled: - url = urljoin(self.root_url, '/taxonomies/addTag/{}'.format(taxonomy_id)) + url = urljoin(self.root_url, 'taxonomies/addTag/{}'.format(taxonomy_id)) response = self._prepare_request('POST', url) return self._check_response(response) def disable_taxonomy_tags(self, taxonomy_id): """Disable all the tags of a taxonomy by id.""" - url = urljoin(self.root_url, '/taxonomies/disableTag/{}'.format(taxonomy_id)) + url = urljoin(self.root_url, 'taxonomies/disableTag/{}'.format(taxonomy_id)) response = self._prepare_request('POST', url) return self._check_response(response) @@ -1906,19 +1909,19 @@ class PyMISP(object): def get_warninglists(self): """Get all the warninglists.""" - url = urljoin(self.root_url, '/warninglists') + url = urljoin(self.root_url, 'warninglists') response = self._prepare_request('GET', url) return self._check_response(response) def get_warninglist(self, warninglist_id): """Get a warninglist by id.""" - url = urljoin(self.root_url, '/warninglists/view/{}'.format(warninglist_id)) + url = urljoin(self.root_url, 'warninglists/view/{}'.format(warninglist_id)) response = self._prepare_request('GET', url) return self._check_response(response) def update_warninglists(self): """Update all the warninglists.""" - url = urljoin(self.root_url, '/warninglists/update') + url = urljoin(self.root_url, 'warninglists/update') response = self._prepare_request('POST', url) return self._check_response(response) @@ -1940,7 +1943,7 @@ class PyMISP(object): query['name'] = warninglist_name if force_enable is not None: query['enabled'] = force_enable - url = urljoin(self.root_url, '/warninglists/toggleEnable') + url = urljoin(self.root_url, 'warninglists/toggleEnable') response = self._prepare_request('POST', url, json.dumps(query)) return self._check_response(response) @@ -1954,7 +1957,7 @@ class PyMISP(object): def check_warninglist(self, value): """Check if IOC values are in warninglist""" - url = urljoin(self.root_url, '/warninglists/checkValue') + url = urljoin(self.root_url, 'warninglists/checkValue') response = self._prepare_request('POST', url, json.dumps(value)) return self._check_response(response) @@ -1962,31 +1965,31 @@ class PyMISP(object): def get_noticelists(self): """Get all the noticelists.""" - url = urljoin(self.root_url, '/noticelists') + url = urljoin(self.root_url, 'noticelists') response = self._prepare_request('GET', url) return self._check_response(response) def get_noticelist(self, noticelist_id): """Get a noticelist by id.""" - url = urljoin(self.root_url, '/noticelists/view/{}'.format(noticelist_id)) + url = urljoin(self.root_url, 'noticelists/view/{}'.format(noticelist_id)) response = self._prepare_request('GET', url) return self._check_response(response) def update_noticelists(self): """Update all the noticelists.""" - url = urljoin(self.root_url, '/noticelists/update') + url = urljoin(self.root_url, 'noticelists/update') response = self._prepare_request('POST', url) return self._check_response(response) def enable_noticelist(self, noticelist_id): """Enable a noticelist by id.""" - url = urljoin(self.root_url, '/noticelists/enableNoticelist/{}/true'.format(noticelist_id)) + url = urljoin(self.root_url, 'noticelists/enableNoticelist/{}/true'.format(noticelist_id)) response = self._prepare_request('POST', url) return self._check_response(response) def disable_noticelist(self, noticelist_id): """Disable a noticelist by id.""" - url = urljoin(self.root_url, '/noticelists/enableNoticelist/{}'.format(noticelist_id)) + url = urljoin(self.root_url, 'noticelists/enableNoticelist/{}'.format(noticelist_id)) response = self._prepare_request('POST', url) return self._check_response(response) @@ -1994,19 +1997,19 @@ class PyMISP(object): def get_galaxies(self): """Get all the galaxies.""" - url = urljoin(self.root_url, '/galaxies') + url = urljoin(self.root_url, 'galaxies') response = self._prepare_request('GET', url) return self._check_response(response) def get_galaxy(self, galaxy_id): """Get a galaxy by id.""" - url = urljoin(self.root_url, '/galaxies/view/{}'.format(galaxy_id)) + url = urljoin(self.root_url, 'galaxies/view/{}'.format(galaxy_id)) response = self._prepare_request('GET', url) return self._check_response(response) def update_galaxies(self): """Update all the galaxies.""" - url = urljoin(self.root_url, '/galaxies/update') + url = urljoin(self.root_url, 'galaxies/update') response = self._prepare_request('POST', url) return self._check_response(response) @@ -2046,7 +2049,7 @@ class PyMISP(object): if tags: if isinstance(tags, list): tags = "&&".join(tags) - url = urljoin(self.root_url, "/events/stix/download/{}/{}/{}/{}/{}".format( + url = urljoin(self.root_url, "events/stix/download/{}/{}/{}/{}/{}".format( event_id, with_attachments, tags, from_date, to_date)) logger.debug("Getting STIX event from %s", url) response = self._prepare_request('GET', url) @@ -2064,7 +2067,7 @@ class PyMISP(object): :param context: Add event level context (event_info,event_member_org,event_source_org,event_distribution,event_threat_level_id,event_analysis,event_date,event_tag) :param ignore: Returns the attributes even if the event isn't published, or the attribute doesn't have the to_ids flag set """ - url = urljoin(self.root_url, '/events/csv/download') + url = urljoin(self.root_url, 'events/csv/download') to_post = {} if eventid: to_post['eventid'] = eventid @@ -2217,7 +2220,7 @@ class PyMISP(object): :extend: Allow the organisation to extend the group ''' to_jsonify = {'sg_id': sharing_group, 'org_id': organisation, 'extend': extend} - url = urljoin(self.root_url, '/sharingGroups/addOrg') + url = urljoin(self.root_url, 'sharingGroups/addOrg') response = self._prepare_request('POST', url, json.dumps(to_jsonify)) return self._check_response(response) @@ -2227,7 +2230,7 @@ class PyMISP(object): :organisation: Organisation's local instance ID, or Organisation's global UUID, or Organisation's name as known to the curent instance ''' to_jsonify = {'sg_id': sharing_group, 'org_id': organisation} - url = urljoin(self.root_url, '/sharingGroups/removeOrg') + url = urljoin(self.root_url, 'sharingGroups/removeOrg') response = self._prepare_request('POST', url, json.dumps(to_jsonify)) return self._check_response(response) @@ -2238,7 +2241,7 @@ class PyMISP(object): :all_orgs: Add all the organisations of the server to the group ''' to_jsonify = {'sg_id': sharing_group, 'server_id': server, 'all_orgs': all_orgs} - url = urljoin(self.root_url, '/sharingGroups/addServer') + url = urljoin(self.root_url, 'sharingGroups/addServer') response = self._prepare_request('POST', url, json.dumps(to_jsonify)) return self._check_response(response) @@ -2248,7 +2251,7 @@ class PyMISP(object): :server: Server's local instance ID, or URL of the Server, or Server's name as known to the curent instance ''' to_jsonify = {'sg_id': sharing_group, 'server_id': server} - url = urljoin(self.root_url, '/sharingGroups/removeServer') + url = urljoin(self.root_url, 'sharingGroups/removeServer') response = self._prepare_request('POST', url, json.dumps(to_jsonify)) return self._check_response(response) @@ -2324,7 +2327,7 @@ class PyMISP(object): return self._check_response(response) def update_object_templates(self): - url = urljoin(self.root_url, '/objectTemplates/update') + url = urljoin(self.root_url, 'objectTemplates/update') response = self._prepare_request('POST', url) return self._check_response(response) diff --git a/pymisp/data/misp-objects b/pymisp/data/misp-objects index 75ae30f..d0886ba 160000 --- a/pymisp/data/misp-objects +++ b/pymisp/data/misp-objects @@ -1 +1 @@ -Subproject commit 75ae30f44df997280255eec60b981b9f376c5ac4 +Subproject commit d0886ba6aff1526868efcd59fccb99e920372f3b diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index 74f92ef..e063535 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -34,7 +34,6 @@ try: HAS_REPORTLAB = True except ImportError: HAS_REPORTLAB = False - print("ReportLab cannot be imported. Please verify that ReportLab is installed on the system.") ######################################################################## @@ -190,6 +189,17 @@ analysis_map = {"0": " Initial (0) Ongoing (1)", "2": " Completed (2)"} +# == Parameters for Sightings == +POSITIVE_SIGHT_COLOR = 'green' +NEGATIVE_SIGHT_COLOR = 'red' +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 +OFFSET = 1 ######################################################################## # "UTILITIES" METHODS. Not meant to be used except for development purposes @@ -315,6 +325,9 @@ 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 == [] : + # Do nothing + return lines_list else: if data_len > len(line_alternation) : logger.warning("Line alternation for PDF display isn't correctly set. Looping on given values only.") @@ -374,6 +387,30 @@ def get_table_styles(): return custom_body_style_col_1, custom_body_style_col_2 +def get_clusters_table_styles(): + ''' + Create and returns the two mains styles for the columns of a table describing a cluster. + :return: two styles, one for each columns of the document, describing the MISP object. + ''' + col1, col2 = get_table_styles() + + custom_body_style_col_1 = ParagraphStyle(name='Column_1_small', + parent=col1, + fontName=FIRST_COL_FONT, + textColor=FIRST_COL_FONT_COLOR, + fontSize=TEXT_FONT_SIZE - 2, + leading=LEADING_SPACE- 1, + alignment=FIRST_COL_ALIGNEMENT) + + custom_body_style_col_2 = ParagraphStyle(name='Column_2_small', + parent=col2, + fontName=SECOND_COL_FONT, + textColor=SECOND_COL_FONT_COLOR, + fontSize=TEXT_FONT_SIZE - 2, + leading=LEADING_SPACE- 1, + alignment=TA_JUSTIFY) + + return custom_body_style_col_1, custom_body_style_col_2 ######################################################################## # Checks @@ -413,18 +450,22 @@ class Value_Formatter(): ''' # ---------------------------------------------------------------------- - def __init__(self, config, col1_style, col2_style): + def __init__(self, config, col1_style, col2_style, col1_small_style, col2_small_style): self.config = config self.col1_style = col1_style self.col2_style = col2_style + self.col1_small_style = col1_small_style + self.col2_small_style = col2_small_style # ---------------------------------------------------------------------- ######################################################################## # General attribut formater - def get_col1_paragraph(self, dirty_string): - return self.get_unoverflowable_paragraph(dirty_string, self.col1_style) + 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) - def get_unoverflowable_paragraph(self, dirty_string, curr_style=None, do_escape_string=True): + def get_unoverflowable_paragraph(self, dirty_string, curr_style=None, do_escape_string=True, do_small=False): ''' Create a paragraph that can fit on a cell displayed one page maximum. This method can be improved (get the exact size of the current frame, and limit the paragraph to this size.) @@ -439,8 +480,12 @@ class Value_Formatter(): else: sanitized_str = dirty_string - if curr_style is None: - curr_style = self.col2_style + if curr_style is None : + if do_small : + curr_style = self.col2_small_style + 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) @@ -721,10 +766,10 @@ class Value_Formatter(): if is_safe_dict_attribute(misp_galaxy, item[1]): return self.get_unoverflowable_paragraph(safe_string(misp_galaxy[item[1]]) + " from " + safe_string(misp_galaxy[item[3]]) + ":" - + safe_string(misp_galaxy[item[4]]), do_escape_string=False) - return self.get_unoverflowable_paragraph(item[2]) + + 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): + def get_galaxy_cluster_name_value(self, misp_cluster, do_small=False): item = ["Name", 'value', "None", "source", "meta", "synonyms"] tmp_text = "" @@ -742,8 +787,8 @@ class Value_Formatter(): tmp_text += " / " tmp_text += safe_string(synonyme) - return self.get_unoverflowable_paragraph(tmp_text, do_escape_string=False) - return self.get_unoverflowable_paragraph(item[2]) + 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(): @@ -826,6 +871,8 @@ class Event_Metadata(): flowable_table.append(create_flowable_table_from_data(data)) + flowable_table.append(PageBreak()) + # Galaxies item = ["Related Galaxies", 'Galaxy', "None"] curr_Galaxy = Galaxy(self.config, self.value_formatter) @@ -962,7 +1009,7 @@ class Attributes(): # There is some attributes for this object 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(Paragraph("Attribute #" + str(i), self.sample_style_sheet['Heading4'])) + flowable_table.append(Paragraph("Attribute #" + str(i+OFFSET), self.sample_style_sheet['Heading4'])) flowable_table += self.create_flowable_table_from_one_attribute(item) i += 1 else: @@ -1036,8 +1083,9 @@ class Attributes(): # Galaxies item = ["Related Galaxies", 'Galaxy', "None"] - curr_Galaxy = Galaxy(self.config, self.value_formatter) - flowable_table += curr_Galaxy.get_galaxy_value(misp_attribute, item) + if is_safe_attribute_table(misp_attribute, item[1]) : + curr_Galaxy = Galaxy(self.config, self.value_formatter) + flowable_table += curr_Galaxy.get_galaxy_value(misp_attribute, item) return flowable_table @@ -1121,11 +1169,7 @@ class Sightings(): :return: a table of flowable to add to the pdf ''' - col1_style, col2_style = get_table_styles() i = 0 - POSITIVE_SIGHT_COLOR = 'green' - NEGATIVE_SIGHT_COLOR = 'red' - MISC_SIGHT_COLOR = 'orange' list_sighting = [0, 0, 0] if is_safe_attribute_table(misp_attribute, item[1]): @@ -1151,6 +1195,7 @@ class Sightings(): return answer_sighting + class Object(): # ---------------------------------------------------------------------- @@ -1161,6 +1206,7 @@ class Object(): # ---------------------------------------------------------------------- + def create_flowable_table_from_objects(self, misp_event, config=None): ''' Returns a list of flowables representing the list of objects of a misp event. @@ -1178,7 +1224,7 @@ class Object(): # There is a list of objects 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(Paragraph("Object #" + str(i), self.sample_style_sheet['Heading3'])) + flowable_table.append(Paragraph("Object #" + str(i+OFFSET), self.sample_style_sheet['Heading3'])) flowable_table += self.create_flowable_table_from_one_object(item, config) i += 1 else: @@ -1187,6 +1233,7 @@ class Object(): return flowable_table + def create_flowable_table_from_one_object(self, misp_object, config=None): ''' Returns a table (flowable) representing the object @@ -1194,7 +1241,6 @@ class Object(): :return: a table representing this misp's object's attributes, to add to the pdf as a flowable ''' data = [] - col1_style, col2_style = get_table_styles() # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, # to_display_if_not_present) are store in the following list @@ -1217,7 +1263,7 @@ class Object(): # Timestamp item = ["Object date", 'timestamp', "None"] - data.append([Paragraph(item[0], col1_style), 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)] @@ -1275,19 +1321,20 @@ class Galaxy(): :param misp_event: A misp event :return: a table of flowables to add to the pdf ''' - flowable_table = [] scheme_alternation = [] curr_color = 0 i = 0 + if is_safe_attribute_table(misp_event, "Galaxy"): # There is some galaxies for this object for curr_galaxy in getattr(misp_event, "Galaxy"): # For each galaxy of the misp object - galaxy_title = Paragraph("Galaxy # " + str(i), self.sample_style_sheet['Heading6']) + txt_title = "Galaxy #" + str(i+OFFSET) + " - " + safe_string(curr_galaxy["name"]) + galaxy_title = Paragraph(txt_title, self.sample_style_sheet['Heading6']) flowable_table.append(galaxy_title) i += 1 @@ -1298,21 +1345,12 @@ class Galaxy(): # Construct the line color scheme and line scheme scheme_alternation += [curr_color] * nb_added_item - # Apply the scheme - # answer_tags = create_flowable_table_from_data(flowable_table) - # Add metadata about clusters curr_cluster = Galaxy_cluster(self.config, self.value_formatter) clusters_metadata = curr_cluster.create_flowable_table_from_galaxy_clusters(curr_galaxy) flowable_table += clusters_metadata - ''' - # Construct the line color scheme and line scheme - scheme_alternation += [curr_color] * nb_added_item - curr_color += 1 if curr_color == 0 else 0 - ''' - else: # No galaxies for this object answer_tags = [self.value_formatter.get_unoverflowable_paragraph("No galaxies")] @@ -1332,15 +1370,15 @@ class Galaxy(): # Name item = ["Name", 'name', "None"] if is_safe_dict_attribute(misp_galaxy, item[1]): - data.append([self.value_formatter.get_col1_paragraph(item[0]), + data.append([self.value_formatter.get_col1_paragraph(item[0], do_small=DO_SMALL_GALAXIES), self.value_formatter.get_galaxy_name_value(misp_galaxy)]) nb_added_item += 1 # Description item = ["Description", 'description', "None"] if is_safe_dict_attribute(misp_galaxy, item[1]): - data.append([self.value_formatter.get_col1_paragraph(item[0]), - self.value_formatter.get_unoverflowable_paragraph(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)]) nb_added_item += 1 flowable_table = [] @@ -1349,6 +1387,7 @@ class Galaxy(): return flowable_table, nb_added_item + class Galaxy_cluster(): # ---------------------------------------------------------------------- @@ -1366,37 +1405,31 @@ class Galaxy_cluster(): ''' data = [] - i = 0 - item = ["Cluster #", 'name', "None"] - if is_safe_dict_attribute(misp_galaxy, "GalaxyCluster"): # There is some clusters for this object - for curr_cluster in misp_galaxy["GalaxyCluster"]: + for i, curr_cluster in enumerate(misp_galaxy["GalaxyCluster"]): - ''' - galaxy_title = [Paragraph("Cluster #" + str(i), self.sample_style_sheet['Heading6'])] - data.append(galaxy_title) - i += 1 - ''' + # 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, color_alternation = [0], line_alternation=[0])) - data.append([self.value_formatter.get_col1_paragraph(item[0]), tmp_flowable_table]) - - # data += tmp_data + tmp_flowable_table.append(create_flowable_table_from_data(tmp_data, col_w=SECOND_LEVEL_GALAXY_WIDTHS, color_alternation = CLUSTER_COLORS, line_alternation=[])) + 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")] - + 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)) - + flowable_table.append(create_flowable_table_from_data(data, col_w=FIRST_LEVEL_GALAXY_WIDTHS, color_alternation = CLUSTER_COLORS)) return flowable_table @@ -1410,15 +1443,19 @@ class Galaxy_cluster(): # Name item = ["Name", 'name', "None"] - data.append([self.value_formatter.get_col1_paragraph(item[0]), - self.value_formatter.get_galaxy_cluster_name_value(misp_cluster)]) + 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)]) - # Description - item = ["Description", 'description', "None"] - data.append([self.value_formatter.get_col1_paragraph(item[0]), - self.value_formatter.get_unoverflowable_paragraph(misp_cluster[item[1]])]) + 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)]) # Refs ? + # item = ["Description", 'description', "None"] + # data.append([self.value_formatter.get_col1_paragraph(item[0]), + # self.value_formatter.get_unoverflowable_paragraph(misp_cluster[item[1]])]) return data @@ -1506,7 +1543,8 @@ def collect_parts(misp_event, config=None): # Get the list of available styles sample_style_sheet = getSampleStyleSheet() col1_style, col2_style = get_table_styles() - curr_val_f = Value_Formatter(config, col1_style, col2_style) + col1_small_style, col2_small_style = get_clusters_table_styles() + 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) diff --git a/setup.py b/setup.py index 5e6c7b9..27ea70f 100644 --- a/setup.py +++ b/setup.py @@ -41,12 +41,13 @@ setup( ], install_requires=['six', 'requests', 'python-dateutil', 'jsonschema', 'python-dateutil', 'enum34;python_version<"3.4"', - 'functools32;python_version<"3.0"', 'reportlab'], + 'functools32;python_version<"3.0"'], extras_require={'fileobjects': ['lief>=0.8', 'python-magic'], 'neo': ['py2neo'], 'openioc': ['beautifulsoup4'], 'virustotal': ['validators'], - 'docs': ['sphinx-autodoc-typehints']}, + 'docs': ['sphinx-autodoc-typehints'], + 'pdfexport': ['reportlab']}, tests_require=[ 'jsonschema', 'python-magic', diff --git a/tests/mispevent_testfiles/event_obj_attr_tag.json b/tests/mispevent_testfiles/event_obj_attr_tag.json index 6b2b637..87ad93a 100644 --- a/tests/mispevent_testfiles/event_obj_attr_tag.json +++ b/tests/mispevent_testfiles/event_obj_attr_tag.json @@ -31,7 +31,7 @@ "name": "file", "sharing_group_id": "0", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", - "template_version": "15", + "template_version": "16", "uuid": "a" }, { diff --git a/tests/mispevent_testfiles/event_obj_def_param.json b/tests/mispevent_testfiles/event_obj_def_param.json index e18fe9e..b6857e8 100644 --- a/tests/mispevent_testfiles/event_obj_def_param.json +++ b/tests/mispevent_testfiles/event_obj_def_param.json @@ -23,7 +23,7 @@ "name": "file", "sharing_group_id": "0", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", - "template_version": "15", + "template_version": "16", "uuid": "a" }, { @@ -48,7 +48,7 @@ "name": "file", "sharing_group_id": "0", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", - "template_version": "15", + "template_version": "16", "uuid": "b" } ] diff --git a/tests/reportlab_testfiles/image_event.json b/tests/reportlab_testfiles/image_event.json index 5cc4933..e619c6d 100644 --- a/tests/reportlab_testfiles/image_event.json +++ b/tests/reportlab_testfiles/image_event.json @@ -715,7 +715,7 @@ "meta-category": "file", "description": "File object describing a file with meta-information", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", - "template_version": "15", + "template_version": "16", "event_id": "1203", "uuid": "1db36cab-7b13-4758-b16a-9e9862d0973e", "timestamp": "1550871228", @@ -887,7 +887,7 @@ "meta-category": "file", "description": "File object describing a file with meta-information", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", - "template_version": "15", + "template_version": "16", "event_id": "1203", "uuid": "3b8f6a45-0b7f-4bea-ad61-0369f01cc306", "timestamp": "1550871228", @@ -1059,7 +1059,7 @@ "meta-category": "file", "description": "File object describing a file with meta-information", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", - "template_version": "15", + "template_version": "16", "event_id": "1203", "uuid": "8cc1ffb8-e4b2-4641-a536-ea843ff9bc7a", "timestamp": "1550871228", @@ -1231,7 +1231,7 @@ "meta-category": "file", "description": "File object describing a file with meta-information", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", - "template_version": "15", + "template_version": "16", "event_id": "1203", "uuid": "89e0ad73-a186-4959-b978-2311ee49e4af", "timestamp": "1550871229", @@ -1403,7 +1403,7 @@ "meta-category": "file", "description": "File object describing a file with meta-information", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", - "template_version": "15", + "template_version": "16", "event_id": "1203", "uuid": "4dbf697b-11ce-447f-85c6-cd02a2365a7f", "timestamp": "1550871229", @@ -1575,7 +1575,7 @@ "meta-category": "file", "description": "File object describing a file with meta-information", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", - "template_version": "15", + "template_version": "16", "event_id": "1203", "uuid": "6860e975-938c-413d-b144-74cde72c25dc", "timestamp": "1550871229", @@ -1747,7 +1747,7 @@ "meta-category": "file", "description": "File object describing a file with meta-information", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", - "template_version": "15", + "template_version": "16", "event_id": "1203", "uuid": "df5dd372-ecd6-4595-ab34-45bff1decb63", "timestamp": "1550871229", @@ -1919,7 +1919,7 @@ "meta-category": "file", "description": "File object describing a file with meta-information", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", - "template_version": "15", + "template_version": "16", "event_id": "1203", "uuid": "3061d73f-2f4f-4c6e-8478-3d5d1e74c1bc", "timestamp": "1550871229", @@ -2091,7 +2091,7 @@ "meta-category": "file", "description": "File object describing a file with meta-information", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", - "template_version": "15", + "template_version": "16", "event_id": "1203", "uuid": "fd57be37-61cc-4452-85b5-518d55586335", "timestamp": "1550871230", @@ -2263,7 +2263,7 @@ "meta-category": "file", "description": "File object describing a file with meta-information", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", - "template_version": "15", + "template_version": "16", "event_id": "1203", "uuid": "56b391e4-f005-4caa-ae12-a90db6664ebd", "timestamp": "1550871270", @@ -2487,4 +2487,4 @@ } ] } -} \ No newline at end of file +} diff --git a/tests/reportlab_testfiles/mainly_objects_1.json b/tests/reportlab_testfiles/mainly_objects_1.json index 61b04a6..733758c 100644 --- a/tests/reportlab_testfiles/mainly_objects_1.json +++ b/tests/reportlab_testfiles/mainly_objects_1.json @@ -70,7 +70,7 @@ "timestamp": "1543921748", "description": "File object describing a file with meta-information", "distribution": "3", - "template_version": "15", + "template_version": "16", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", "comment": "", "name": "file", @@ -114,7 +114,7 @@ "timestamp": "1543921750", "description": "File object describing a file with meta-information", "distribution": "3", - "template_version": "15", + "template_version": "16", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", "comment": "", "name": "file", @@ -158,7 +158,7 @@ "timestamp": "1543921751", "description": "File object describing a file with meta-information", "distribution": "3", - "template_version": "15", + "template_version": "16", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", "comment": "", "name": "file", @@ -482,7 +482,7 @@ "timestamp": "1543921755", "description": "File object describing a file with meta-information", "distribution": "3", - "template_version": "15", + "template_version": "16", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", "comment": "", "name": "file", @@ -875,7 +875,7 @@ "timestamp": "1543921759", "description": "File object describing a file with meta-information", "distribution": "3", - "template_version": "15", + "template_version": "16", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", "comment": "", "name": "file", @@ -988,7 +988,7 @@ "timestamp": "1543921762", "description": "File object describing a file with meta-information", "distribution": "3", - "template_version": "15", + "template_version": "16", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", "comment": "", "name": "file", @@ -1089,4 +1089,4 @@ } ] } -} \ No newline at end of file +} diff --git a/tests/reportlab_testfiles/mainly_objects_2.json b/tests/reportlab_testfiles/mainly_objects_2.json index 0c01864..3471c1f 100644 --- a/tests/reportlab_testfiles/mainly_objects_2.json +++ b/tests/reportlab_testfiles/mainly_objects_2.json @@ -82,7 +82,7 @@ "timestamp": "1543922168", "description": "File object describing a file with meta-information", "distribution": "3", - "template_version": "15", + "template_version": "16", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", "comment": "", "name": "file", @@ -126,7 +126,7 @@ "timestamp": "1543922169", "description": "File object describing a file with meta-information", "distribution": "3", - "template_version": "15", + "template_version": "16", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", "comment": "", "name": "file", @@ -450,7 +450,7 @@ "timestamp": "1543922173", "description": "File object describing a file with meta-information", "distribution": "3", - "template_version": "15", + "template_version": "16", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", "comment": "", "name": "file", @@ -857,7 +857,7 @@ "timestamp": "1543922178", "description": "File object describing a file with meta-information", "distribution": "3", - "template_version": "15", + "template_version": "16", "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", "comment": "", "name": "file", @@ -974,4 +974,4 @@ } ] } -} \ No newline at end of file +} diff --git a/tests/test_reportlab.py b/tests/test_reportlab.py index 3ccb497..3f4cf2f 100644 --- a/tests/test_reportlab.py +++ b/tests/test_reportlab.py @@ -9,8 +9,13 @@ import unittest from pymisp import MISPEvent from pymisp.tools import reportlab_generator -manual_testing = True +manual_testing = False +if sys.version_info < (3, 6): + print('This test suite requires Python 3.6+, breaking.') + sys.exit(0) +else: + from pymisp import reportlab_generator class TestMISPEvent(unittest.TestCase): @@ -226,7 +231,6 @@ class TestMISPEvent(unittest.TestCase): if self.check_python_2(): self.assertTrue(True) else: - config = {} config[self.moduleconfig[0]] = "http://localhost:8080" config[self.moduleconfig[1]] = "My Wonderful CERT" @@ -309,6 +313,8 @@ class TestMISPEvent(unittest.TestCase): config[self.moduleconfig[0]] = "http://localhost:8080" config[self.moduleconfig[1]] = "My Wonderful CERT" config[self.moduleconfig[2]] = True + config[self.moduleconfig[3]] = True + file_nb = str(len(os.listdir(self.test_batch_folder))) i = 0 diff --git a/tests/testlive_comprehensive.py b/tests/testlive_comprehensive.py index 4c7e673..52b2676 100644 --- a/tests/testlive_comprehensive.py +++ b/tests/testlive_comprehensive.py @@ -496,7 +496,7 @@ class TestComprehensive(unittest.TestCase): self.assertEqual(new_obj['Object']['distribution'], str(Distribution.inherit.value)) self.assertEqual(new_obj['Object']['Attribute'][0]['distribution'], str(Distribution.inherit.value)) # Object - edit - clean_obj = MISPObject(**new_obj['Object']) + clean_obj = MISPObject(strict=True, **new_obj['Object']) clean_obj.from_dict(**new_obj['Object']) clean_obj.add_attribute('filename', value='blah.exe') new_obj = self.user_misp_connector.edit_object(clean_obj) From e6291e71d5b739a15bb5b35be6dcb4bb57629afe Mon Sep 17 00:00:00 2001 From: Falconieri Date: Fri, 1 Mar 2019 09:21:48 +0100 Subject: [PATCH 06/10] fix: [exportpdf] None if no Galaxies bug --- pymisp/tools/reportlab_generator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index e063535..e1c284e 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -871,12 +871,13 @@ class Event_Metadata(): flowable_table.append(create_flowable_table_from_data(data)) - flowable_table.append(PageBreak()) # Galaxies item = ["Related Galaxies", 'Galaxy', "None"] - curr_Galaxy = Galaxy(self.config, self.value_formatter) - flowable_table += curr_Galaxy.get_galaxy_value(misp_event, item) + if is_safe_attribute_table(misp_event, item[1]) : + flowable_table.append(PageBreak()) + curr_Galaxy = Galaxy(self.config, self.value_formatter) + flowable_table += curr_Galaxy.get_galaxy_value(misp_event, item) return flowable_table From 39b4677bf9c0f11ac866b04982c5b6afa2e2e38d Mon Sep 17 00:00:00 2001 From: Falconieri Date: Fri, 1 Mar 2019 09:55:42 +0100 Subject: [PATCH 07/10] fix: [exportpdf] switch page size to A4 --- pymisp/tools/reportlab_generator.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index b5ab2b4..d7aa975 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -25,6 +25,7 @@ try: from reportlab.pdfbase.pdfdoc import PDFDictionary, PDFInfo from reportlab.lib import colors from reportlab.lib.utils import ImageReader + from reportlab.lib.pagesizes import A4 from reportlab.platypus import SimpleDocTemplate, Paragraph, PageBreak, Spacer, Table, TableStyle, Flowable, Image @@ -146,8 +147,15 @@ SECOND_COL_ALIGNEMENT = TA_LEFT TEXT_FONT_SIZE = 8 LEADING_SPACE = 7 + +# Small clusters fonts +SMALL_FONT_SIZE = TEXT_FONT_SIZE - 1 +SMALL_LEADING_SPACE = LEADING_SPACE +SMALL_COL1_ALIGMENT = FIRST_COL_ALIGNEMENT +SMALL_COL2_ALIGMENT = TA_JUSTIFY + EXPORT_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' -COL_WIDTHS = ['30%', '75%'] # colWidths='*' # Not documented but does exist +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) @@ -388,6 +396,7 @@ def get_table_styles(): return custom_body_style_col_1, custom_body_style_col_2 + def get_clusters_table_styles(): ''' Create and returns the two mains styles for the columns of a table describing a cluster. @@ -399,17 +408,17 @@ def get_clusters_table_styles(): parent=col1, fontName=FIRST_COL_FONT, textColor=FIRST_COL_FONT_COLOR, - fontSize=TEXT_FONT_SIZE - 2, - leading=LEADING_SPACE- 1, - alignment=FIRST_COL_ALIGNEMENT) + fontSize=SMALL_FONT_SIZE, + leading=SMALL_LEADING_SPACE, + alignment=SMALL_COL1_ALIGMENT) custom_body_style_col_2 = ParagraphStyle(name='Column_2_small', parent=col2, fontName=SECOND_COL_FONT, textColor=SECOND_COL_FONT_COLOR, - fontSize=TEXT_FONT_SIZE - 2, - leading=LEADING_SPACE- 1, - alignment=TA_JUSTIFY) + fontSize=SMALL_FONT_SIZE, + leading=SMALL_LEADING_SPACE, + alignment=SMALL_COL2_ALIGMENT) return custom_body_style_col_1, custom_body_style_col_2 @@ -1622,7 +1631,7 @@ def convert_event_in_pdf_buffer(misp_event, config=None): # DEBUG / TO DELETE : curr_document = SimpleDocTemplate('myfile.pdf') curr_document = SimpleDocTemplate(pdf_buffer, - pagesize=PAGESIZE, + pagesize=A4, topMargin=BASE_MARGIN, leftMargin=BASE_MARGIN, rightMargin=BASE_MARGIN, From ead9cec7d3f8907ba55b2f91a5f481310a3fdc0d Mon Sep 17 00:00:00 2001 From: Falconieri Date: Fri, 1 Mar 2019 09:56:38 +0100 Subject: [PATCH 08/10] fix: [exportpdf] switch page size to A4 --- pymisp/tools/reportlab_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index d7aa975..62c7e46 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -161,7 +161,7 @@ ROW_HEIGHT = 5 * mm # 4.5 * mm (a bit too short to allow vertical align TODO : 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 = (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 # == Parameters for error handling for content too long to fit on a page == @@ -1631,7 +1631,7 @@ def convert_event_in_pdf_buffer(misp_event, config=None): # DEBUG / TO DELETE : curr_document = SimpleDocTemplate('myfile.pdf') curr_document = SimpleDocTemplate(pdf_buffer, - pagesize=A4, + pagesize=PAGESIZE, topMargin=BASE_MARGIN, leftMargin=BASE_MARGIN, rightMargin=BASE_MARGIN, From b2c5477cd9d12538ba58e8fa8fb263516fb1aea7 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Fri, 1 Mar 2019 10:45:44 +0100 Subject: [PATCH 09/10] fix: [exportpdf] Add suggestions (UX) --- pymisp/tools/reportlab_generator.py | 55 +++++++++++++++++++---------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index 62c7e46..54aa742 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -27,7 +27,7 @@ try: from reportlab.lib.utils import ImageReader from reportlab.lib.pagesizes import A4 - from reportlab.platypus import SimpleDocTemplate, Paragraph, PageBreak, Spacer, Table, TableStyle, Flowable, Image + 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 @@ -130,6 +130,8 @@ moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Act # == Row colors of the table (alternating) == EVEN_COLOR = colors.whitesmoke ODD_COLOR = colors.lightgrey +EVEN_COLOR_GALAXY = colors.powderblue +ODD_COLOR_GALAXY = colors.lavenderblush # == Lines parameters of the table == LINE_COLOR = colors.lightslategray @@ -163,15 +165,17 @@ ROW_HEIGHT_FOR_TAGS = 4 * mm # 4.5 * mm (a bit too short to allow vertical alig # == Whole document margins and size == 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. # == Parameters for error handling for content too long to fit on a page == -FRAME_MAX_HEIGHT = 500 # 650 # Ad hoc value for a A4 page -FRAME_MAX_WIDTH = 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 = "
[Too long to fit on a single page. Cropped]" # == Parameters for error handling for image too big to fit on a page == -FRAME_PICTURE_MAX_WIDTH = 88 * mm -FRAME_PICTURE_MAX_HEIGHT = 195 * 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 @@ -254,7 +258,7 @@ 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): +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) @@ -268,7 +272,7 @@ 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) + 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() @@ -278,7 +282,7 @@ def create_flowable_table_from_data(data, col_w=COL_WIDTHS, color_alternation=No return curr_table -def alternate_colors_style_generator(data, color_alternation): +def alternate_colors_style_generator(data, color_alternation, galaxy_colors=True): ''' Create a style, applicable on a table that will be built with parameter's data, with alternated background color for each line. @@ -296,20 +300,20 @@ def alternate_colors_style_generator(data, color_alternation): # For each line, generate a tuple giving to a line a color for each in range(data_len): if each % 2 == 0: - bg_color = EVEN_COLOR + bg_color = EVEN_COLOR if not galaxy_colors else EVEN_COLOR_GALAXY else: - bg_color = ODD_COLOR + bg_color = ODD_COLOR if not galaxy_colors else ODD_COLOR_GALAXY color_list.append(('BACKGROUND', (0, each), (-1, each), bg_color)) else: - if data_len > len(color_alternation) : - logger.warning("Line alternation for PDF display isn't correctly set. Looping on given values only.") + # if data_len > len(color_alternation) : + # logger.warning("Line alternation for PDF display isn't correctly set. Looping on given values only.") # 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: - bg_color = EVEN_COLOR + bg_color = EVEN_COLOR if not galaxy_colors else EVEN_COLOR_GALAXY else: - bg_color = ODD_COLOR + bg_color = ODD_COLOR if not galaxy_colors else ODD_COLOR_GALAXY color_list.append(('BACKGROUND', (0, each), (-1, each), bg_color)) return color_list @@ -338,8 +342,8 @@ def lines_style_generator(data, line_alternation): # Do nothing return lines_list else: - if data_len > len(line_alternation) : - logger.warning("Line alternation for PDF display isn't correctly set. Looping on given values only.") + # if data_len > len(line_alternation) : + # logger.warning("Line alternation for PDF display isn't correctly set. Looping on given values only.") # For each line, generate a tuple giving to a line a color for each in range(data_len): @@ -784,7 +788,6 @@ class Value_Formatter(): tmp_text = "" if is_safe_dict_attribute(misp_cluster, item[1]): - print(misp_cluster[item[1]]) tmp_text += safe_string(misp_cluster[item[1]]) #if is_safe_dict_attribute(misp_cluster, item[3]) : @@ -1020,7 +1023,10 @@ class Attributes(): # There is some attributes for this object 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(Indenter(left=-INDENT_SIZE_HEADING)) + flowable_table += self.create_flowable_table_from_one_attribute(item) i += 1 else: @@ -1092,11 +1098,14 @@ 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]) : 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) + flowable_table.append(Indenter(left=-INDENT_SIZE)) return flowable_table @@ -1234,7 +1243,9 @@ class Object(): # There is a list of objects 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(Indenter(left=-INDENT_SIZE_HEADING)) flowable_table += self.create_flowable_table_from_one_object(item, config) i += 1 else: @@ -1281,7 +1292,9 @@ class Object(): # Handle all the attributes if is_safe_attribute(misp_object, "Attribute"): curr_attributes = Attributes(self.config, self.value_formatter) + data.append(Indenter(left=INDENT_SIZE)) data += curr_attributes.create_flowable_table_from_attributes(misp_object) + data.append(Indenter(left=-INDENT_SIZE)) # Add a page break at the end of an object data.append(PageBreak()) @@ -1317,7 +1330,9 @@ class Galaxy(): 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']) + 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 : flowable_table.append(self.value_formatter.get_unoverflowable_paragraph(item[2])) @@ -1344,7 +1359,9 @@ class Galaxy(): txt_title = "Galaxy #" + str(i+OFFSET) + " - " + safe_string(curr_galaxy["name"]) galaxy_title = Paragraph(txt_title, self.sample_style_sheet['Heading6']) + flowable_table.append(Indenter(left=INDENT_SIZE_HEADING)) flowable_table.append(galaxy_title) + flowable_table.append(Indenter(left=-INDENT_SIZE_HEADING)) i += 1 # Add metadata about the Galaxy @@ -1430,7 +1447,7 @@ class Galaxy_cluster(): # 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=[])) + 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: @@ -1438,7 +1455,7 @@ class Galaxy_cluster(): 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)) + 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 From 1b8921d82053d022e698ad123f345151d2c27b79 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Fri, 1 Mar 2019 11:12:04 +0100 Subject: [PATCH 10/10] fix: [exportpdf] fix empty object/attribute/galaxy bugs --- pymisp/tools/reportlab_generator.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index 54aa742..1cab9c2 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -887,7 +887,7 @@ class Event_Metadata(): # Galaxies item = ["Related Galaxies", 'Galaxy', "None"] - if is_safe_attribute_table(misp_event, item[1]) : + if is_safe_attribute_table(misp_event, item[1]) and is_in_config(self.config, 3): flowable_table.append(PageBreak()) curr_Galaxy = Galaxy(self.config, self.value_formatter) flowable_table += curr_Galaxy.get_galaxy_value(misp_event, item) @@ -1101,7 +1101,7 @@ class Attributes(): # Galaxies item = ["Related Galaxies", 'Galaxy', "None"] - if is_safe_attribute_table(misp_attribute, item[1]) : + 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) @@ -1329,7 +1329,6 @@ class Galaxy(): # 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']) - flowable_table.append(Indenter(left=INDENT_SIZE_HEADING)) flowable_table.append(galaxy_title) flowable_table.append(Indenter(left=-INDENT_SIZE_HEADING)) @@ -1594,14 +1593,16 @@ def collect_parts(misp_event, config=None): flowables.append(subtitle) flowables += table_general_metainformation - flowables.append(PageBreak()) + if is_safe_attribute_table(misp_event, "Attribute") : + flowables.append(PageBreak()) event_attributes_title = Paragraph("Attributes", sample_style_sheet['Heading2']) table_direct_attributes = curr_attr.create_flowable_table_from_attributes(misp_event) flowables.append(event_attributes_title) flowables += table_direct_attributes - flowables.append(PageBreak()) + if is_safe_attribute_table(misp_event, "Object") : + flowables.append(PageBreak()) event_objects_title = Paragraph("Objects", sample_style_sheet['Heading2']) table_objects = curr_object.create_flowable_table_from_objects(misp_event)