From 8913e9151afd072b5f27e0367490b76657314289 Mon Sep 17 00:00:00 2001 From: DemeritCowboy Date: Mon, 7 Oct 2019 22:33:55 -0400 Subject: [PATCH] fix email processor dropping attachments --- CRM/Admin/Form/Setting/Miscellaneous.php | 2 + CRM/Admin/Form/SettingTrait.php | 4 + CRM/Core/BAO/File.php | 52 +++++--- CRM/Core/Config/MagicMerge.php | 1 + CRM/Utils/DeprecatedUtils.php | 3 +- api/v3/System/setting-whitelist.txt | 1 + settings/Core.setting.php | 22 +++- .../Utils/Mail/EmailProcessorInboundTest.php | 121 +++++++++++++++++ .../inbound/test_message_many_attachments.eml | 123 ++++++++++++++++++ 9 files changed, 311 insertions(+), 18 deletions(-) create mode 100644 tests/phpunit/CRM/Utils/Mail/EmailProcessorInboundTest.php create mode 100644 tests/phpunit/CRM/Utils/Mail/data/inbound/test_message_many_attachments.eml diff --git a/CRM/Admin/Form/Setting/Miscellaneous.php b/CRM/Admin/Form/Setting/Miscellaneous.php index 8b92a4b411..d1277923a8 100644 --- a/CRM/Admin/Form/Setting/Miscellaneous.php +++ b/CRM/Admin/Form/Setting/Miscellaneous.php @@ -38,6 +38,7 @@ class CRM_Admin_Form_Setting_Miscellaneous extends CRM_Admin_Form_Setting { protected $_settings = [ 'max_attachments' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'max_attachments_backend' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'contact_undelete' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'empoweredBy' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'logging' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, @@ -73,6 +74,7 @@ class CRM_Admin_Form_Setting_Miscellaneous extends CRM_Admin_Form_Setting { $this->assign('pure_config_settings', [ 'empoweredBy', 'max_attachments', + 'max_attachments_backend', 'maxFileSize', 'secondDegRelPermissions', 'recentItemsMaxCount', diff --git a/CRM/Admin/Form/SettingTrait.php b/CRM/Admin/Form/SettingTrait.php index 1642b79ddf..ec6e15cfa7 100644 --- a/CRM/Admin/Form/SettingTrait.php +++ b/CRM/Admin/Form/SettingTrait.php @@ -250,6 +250,10 @@ trait CRM_Admin_Form_SettingTrait { //temp hack @todo fix to get from metadata $this->addRule('max_attachments', ts('Value should be a positive number'), 'positiveInteger'); } + if ($setting == 'max_attachments_backend') { + //temp hack @todo fix to get from metadata + $this->addRule('max_attachments_backend', ts('Value should be a positive number'), 'positiveInteger'); + } if ($setting == 'maxFileSize') { //temp hack $this->addRule('maxFileSize', ts('Value should be a positive number'), 'positiveInteger'); diff --git a/CRM/Core/BAO/File.php b/CRM/Core/BAO/File.php index a0a28476ed..7bf3ae3918 100644 --- a/CRM/Core/BAO/File.php +++ b/CRM/Core/BAO/File.php @@ -40,6 +40,13 @@ class CRM_Core_BAO_File extends CRM_Core_DAO_File { public static $_signableFields = ['entityTable', 'entityID', 'fileID']; + /** + * If there is no setting configured on the admin screens, maximum number + * of attachments to try to process when given a list of attachments to + * process. + */ + const DEFAULT_MAX_ATTACHMENTS_BACKEND = 100; + /** * Takes an associative array and creates a File object. * @@ -596,24 +603,37 @@ AND CEF.entity_id = %2"; * @param int $entityID */ public static function processAttachment(&$params, $entityTable, $entityID) { - $numAttachments = Civi::settings()->get('max_attachments'); + $numAttachments = Civi::settings()->get('max_attachments_backend') ?? self::DEFAULT_MAX_ATTACHMENTS_BACKEND; for ($i = 1; $i <= $numAttachments; $i++) { - if ( - isset($params["attachFile_$i"]) && - is_array($params["attachFile_$i"]) - ) { - self::filePostProcess( - $params["attachFile_$i"]['location'], - NULL, - $entityTable, - $entityID, - NULL, - TRUE, - $params["attachFile_$i"], - "attachFile_$i", - $params["attachFile_$i"]['type'] - ); + if (isset($params["attachFile_$i"])) { + /** + * Moved the second condition into its own if block to avoid changing + * how it works if there happens to be an entry that is not an array, + * since we now might exit loop early via newly added break below. + */ + if (is_array($params["attachFile_$i"])) { + self::filePostProcess( + $params["attachFile_$i"]['location'], + NULL, + $entityTable, + $entityID, + NULL, + TRUE, + $params["attachFile_$i"], + "attachFile_$i", + $params["attachFile_$i"]['type'] + ); + } + } + else { + /** + * No point looping 100 times if there aren't any more. + * This assumes the array is continuous and doesn't skip array keys, + * but (a) where would it be doing that, and (b) it would have caused + * problems before anyway if there were skipped keys. + */ + break; } } } diff --git a/CRM/Core/Config/MagicMerge.php b/CRM/Core/Config/MagicMerge.php index 30188ce3bf..4fedbc69af 100644 --- a/CRM/Core/Config/MagicMerge.php +++ b/CRM/Core/Config/MagicMerge.php @@ -168,6 +168,7 @@ class CRM_Core_Config_MagicMerge { 'maxFileSize' => ['setting'], // renamed. 'maxAttachments' => ['setting', 'max_attachments'], + 'maxAttachmentsBackend' => ['setting', 'max_attachments_backend'], 'monetaryDecimalPoint' => ['setting'], 'monetaryThousandSeparator' => ['setting'], 'moneyformat' => ['setting'], diff --git a/CRM/Utils/DeprecatedUtils.php b/CRM/Utils/DeprecatedUtils.php index 3ed6d8f862..0045346efe 100644 --- a/CRM/Utils/DeprecatedUtils.php +++ b/CRM/Utils/DeprecatedUtils.php @@ -879,7 +879,8 @@ function _civicrm_api3_deprecated_activity_buildmailparams($result, $activityTyp $params['activity_date_time'] = $result['date']; $params['details'] = $result['body']; - for ($i = 1; $i <= 5; $i++) { + $numAttachments = Civi::settings()->get('max_attachments_backend') ?? CRM_Core_BAO_File::DEFAULT_MAX_ATTACHMENTS_BACKEND; + for ($i = 1; $i <= $numAttachments; $i++) { if (isset($result["attachFile_$i"])) { $params["attachFile_$i"] = $result["attachFile_$i"]; } diff --git a/api/v3/System/setting-whitelist.txt b/api/v3/System/setting-whitelist.txt index 5f4b74bd9b..a367414635 100644 --- a/api/v3/System/setting-whitelist.txt +++ b/api/v3/System/setting-whitelist.txt @@ -32,6 +32,7 @@ mailerJobSize mailerJobsMax maxFileSize max_attachments +max_attachments_backend replyTo secondDegRelPermissions securityAlert diff --git a/settings/Core.setting.php b/settings/Core.setting.php index 68d82a2367..033d69c18a 100644 --- a/settings/Core.setting.php +++ b/settings/Core.setting.php @@ -387,7 +387,27 @@ return [ 'title' => ts('Maximum Attachments'), 'is_domain' => 1, 'is_contact' => 0, - 'description' => ts('Maximum number of files (documents, images, etc.) which can be attached to emails or activities.'), + 'description' => ts('Maximum number of files (documents, images, etc.) which can be attached to emails or activities. This setting applies to UI forms and limits the number of fields available on the form.'), + 'help_text' => NULL, + ], + 'max_attachments_backend' => [ + 'group_name' => 'CiviCRM Preferences', + 'group' => 'core', + 'name' => 'max_attachments_backend', + 'legacy_key' => 'maxAttachmentsBackend', + 'type' => 'Integer', + 'quick_form_type' => 'Element', + 'html_type' => 'text', + 'html_attributes' => [ + 'size' => 2, + 'maxlength' => 8, + ], + 'default' => CRM_Core_BAO_File::DEFAULT_MAX_ATTACHMENTS_BACKEND, + 'add' => '5.20', + 'title' => ts('Maximum Attachments For Backend Processes'), + 'is_domain' => 1, + 'is_contact' => 0, + 'description' => ts('Maximum number of files (documents, images, etc.) which can be processed during backend processing such as automated inbound email processing. This should be a big number higher than the other Maximum Attachments setting above. This setting here merely provides an upper limit to prevent attacks that might overload the server.'), 'help_text' => NULL, ], 'maxFileSize' => [ diff --git a/tests/phpunit/CRM/Utils/Mail/EmailProcessorInboundTest.php b/tests/phpunit/CRM/Utils/Mail/EmailProcessorInboundTest.php new file mode 100644 index 0000000000..c461e0543d --- /dev/null +++ b/tests/phpunit/CRM/Utils/Mail/EmailProcessorInboundTest.php @@ -0,0 +1,121 @@ +mailSettingsId = $this->callAPISuccess('MailSettings', 'create', [ + 'name' => 'local', + 'protocol' => 'Localdir', + 'source' => __DIR__ . '/data/mail', + 'domain' => 'example.com', + // a little weird - is_default=0 means for inbound email processing + 'is_default' => '0', + 'domain_id' => 1, + ])['id']; + } + + public function tearDown() { + CRM_Utils_File::cleanDir(__DIR__ . '/data/mail'); + $this->callAPISuccess('MailSettings', 'delete', [ + 'id' => $this->mailSettingsId, + ]); + parent::tearDown(); + } + + /** + * Fetch activities with many attachments + * + * In particular the default limit for the UI is 3, which is how this came up + * because it was also being used as a limit for backend processes. So we + * test 4, which is bigger than 3 (unless running on a 2-bit CPU). + */ + public function testFetchActivitiesWithManyAttachments() { + $mail = 'test_message_many_attachments.eml'; + + // paranoid check that settings are the standard defaults + $currentUIMax = Civi::settings()->get('max_attachments'); + $currentBackendMax = Civi::settings()->get('max_attachments_backend'); + if ($currentUIMax > 3) { + Civi::settings()->set('max_attachments', 3); + } + if ($currentBackendMax < CRM_Core_BAO_File::DEFAULT_MAX_ATTACHMENTS_BACKEND) { + Civi::settings()->set('max_attachments_backend', CRM_Core_BAO_File::DEFAULT_MAX_ATTACHMENTS_BACKEND); + } + + // create some contacts + $senderContactId = $this->individualCreate([], 1); + $senderContact = $this->callAPISuccess('Contact', 'getsingle', [ + 'id' => $senderContactId, + ]); + $recipientContactId = $this->individualCreate([], 2); + $recipientContact = $this->callAPISuccess('Contact', 'getsingle', [ + 'id' => $recipientContactId, + ]); + + $templateFillData = [ + 'the_date' => date('r'), + 'from_name' => $senderContact['display_name'], + 'from_email' => $senderContact['email'], + 'to_email' => $recipientContact['email'], + ]; + + // Retrieve the template and insert our data like current dates + $file_contents = file_get_contents(__DIR__ . '/data/inbound/' . $mail); + foreach ($templateFillData as $field => $value) { + $file_contents = str_replace("%%{$field}%%", $value, $file_contents); + } + // put it in the mail dir + file_put_contents(__DIR__ . '/data/mail/' . $mail, $file_contents); + + // run the job + $this->callAPISuccess('job', 'fetch_activities', []); + + // check that file was removed from mail dir + $this->assertFalse(file_exists(__DIR__ . '/data/mail/' . $mail)); + + // get the filed activity, by sender contact id + $activities = $this->callAPISuccess('Activity', 'get', [ + 'source_contact_id' => $senderContact['id'], + ]); + $this->assertEquals(1, $activities['count']); + + // check subject + $activity = $activities['values'][$activities['id']]; + $this->assertEquals('Testing 4 attachments', $activity['subject']); + + // Check target is our recipient + $targets = $this->callAPISuccess('ActivityContact', 'get', [ + 'activity_id' => $activity['id'], + 'record_type_id' => 'Activity Targets', + ]); + $this->assertEquals($recipientContact['id'], $targets['values'][$targets['id']]['contact_id']); + + // Check we have 4 attachments + $attachments = $this->callAPISuccess('Attachment', 'get', [ + 'entity_id' => $activity['id'], + 'entity_table' => 'civicrm_activity', + ]); + $this->assertEquals(4, $attachments['count']); + + // reset in case it was different from defaults + Civi::settings()->set('max_attachments', $currentUIMax); + Civi::settings()->set('max_attachments_backend', $currentBackendMax); + } + +} diff --git a/tests/phpunit/CRM/Utils/Mail/data/inbound/test_message_many_attachments.eml b/tests/phpunit/CRM/Utils/Mail/data/inbound/test_message_many_attachments.eml new file mode 100644 index 0000000000..6ee592f589 --- /dev/null +++ b/tests/phpunit/CRM/Utils/Mail/data/inbound/test_message_many_attachments.eml @@ -0,0 +1,123 @@ +MIME-Version: 1.0 +Date: %%the_date%% +Message-ID: +Subject: Testing 4 attachments +From: %%from_name%% <%%from_email%%> +To: %%to_email%% +Content-Type: multipart/mixed; boundary="000000000000a2ad5f059325db35" + +--000000000000a2ad5f059325db35 +Content-Type: multipart/alternative; boundary="000000000000a2ad5a059325db33" + +--000000000000a2ad5a059325db33 +Content-Type: text/plain; charset="UTF-8" + +test + +--000000000000a2ad5a059325db33 +Content-Type: text/html; charset="UTF-8" + +
test
+ +--000000000000a2ad5a059325db33-- +--000000000000a2ad5f059325db35 +Content-Type: image/gif; name="c.gif" +Content-Disposition: attachment; filename="c.gif" +Content-Transfer-Encoding: base64 +X-Attachment-Id: f_k0v4qdju2 +Content-ID: + +R0lGODlhPwA+APcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBV +ZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDV +mQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMr +zDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq +/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2Yr +AGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaq +M2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kA +ZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmA +mZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/ +zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV +/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/ +AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9V +M/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//V +Zv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAA/AD4A +AAjCAPcJHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuX +IgHInAnT4MybOGvi3JmzJc+fNFcCHQpAJdCCP1HyVLjU5E6GT0tGhdpzZNWGV0NmXTgVZFesN62G +jbi1Y1muY7WmfXh2Y9uEbzPGPTgXY92BXz/eFbjXYl6Ef/XODSz4bVOnh/EmRkx08cnGhKVClqmT +aE2CSS9r3sy5s+fPoEOLHk26tOnTqFPXDAgAOw== +--000000000000a2ad5f059325db35 +Content-Type: image/gif; name="b.gif" +Content-Disposition: attachment; filename="b.gif" +Content-Transfer-Encoding: base64 +X-Attachment-Id: f_k0v4qdjj1 +Content-ID: + +R0lGODlhPwA+APcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBV +ZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDV +mQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMr +zDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq +/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2Yr +AGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaq +M2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kA +ZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmA +mZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/ +zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV +/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/ +AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9V +M/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//V +Zv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAA/AD4A +AAjCAPcJHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuX +IgHInAnT4MybOGvi3JmzJc+fNFcCHQpAJdCCP1HyVLjU5E6GT0tGhdpzZNWGV0NmXTgVZFesN62G +jbi1Y1muY7WmfXh2Y9uEbzPGPTgXY92BXz/eFbjXYl6Ef/XODSz4bVOnh/EmRkx08cnGhKVClqmT +aE2CSS9r3sy5s+fPoEOLHk26tOnTqFPXDAgAOw== +--000000000000a2ad5f059325db35 +Content-Type: image/gif; name="d.gif" +Content-Disposition: attachment; filename="d.gif" +Content-Transfer-Encoding: base64 +X-Attachment-Id: f_k0v4qdk73 +Content-ID: + +R0lGODlhPwA+APcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBV +ZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDV +mQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMr +zDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq +/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2Yr +AGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaq +M2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kA +ZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmA +mZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/ +zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV +/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/ +AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9V +M/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//V +Zv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAA/AD4A +AAjCAPcJHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuX +IgHInAnT4MybOGvi3JmzJc+fNFcCHQpAJdCCP1HyVLjU5E6GT0tGhdpzZNWGV0NmXTgVZFesN62G +jbi1Y1muY7WmfXh2Y9uEbzPGPTgXY92BXz/eFbjXYl6Ef/XODSz4bVOnh/EmRkx08cnGhKVClqmT +aE2CSS9r3sy5s+fPoEOLHk26tOnTqFPXDAgAOw== +--000000000000a2ad5f059325db35 +Content-Type: image/gif; name="a.gif" +Content-Disposition: attachment; filename="a.gif" +Content-Transfer-Encoding: base64 +X-Attachment-Id: f_k0v4qdiv0 +Content-ID: + +R0lGODlhPwA+APcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBV +ZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDV +mQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMr +zDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq +/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2Yr +AGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaq +M2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kA +ZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmA +mZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/ +zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV +/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/ +AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9V +M/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//V +Zv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAA/AD4A +AAjCAPcJHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuX +IgHInAnT4MybOGvi3JmzJc+fNFcCHQpAJdCCP1HyVLjU5E6GT0tGhdpzZNWGV0NmXTgVZFesN62G +jbi1Y1muY7WmfXh2Y9uEbzPGPTgXY92BXz/eFbjXYl6Ef/XODSz4bVOnh/EmRkx08cnGhKVClqmT +aE2CSS9r3sy5s+fPoEOLHk26tOnTqFPXDAgAOw== +--000000000000a2ad5f059325db35-- -- 2.25.1