fix email processor dropping attachments
authorDemeritCowboy <demeritcowboy@hotmail.com>
Tue, 8 Oct 2019 02:33:55 +0000 (22:33 -0400)
committerDemeritCowboy <demeritcowboy@hotmail.com>
Tue, 8 Oct 2019 02:53:49 +0000 (22:53 -0400)
CRM/Admin/Form/Setting/Miscellaneous.php
CRM/Admin/Form/SettingTrait.php
CRM/Core/BAO/File.php
CRM/Core/Config/MagicMerge.php
CRM/Utils/DeprecatedUtils.php
api/v3/System/setting-whitelist.txt
settings/Core.setting.php
tests/phpunit/CRM/Utils/Mail/EmailProcessorInboundTest.php [new file with mode: 0644]
tests/phpunit/CRM/Utils/Mail/data/inbound/test_message_many_attachments.eml [new file with mode: 0644]

index 8b92a4b411f5e23960565f56cadf68c2275e9252..d1277923a82f94fea7c7f63d06231062f61f9f20 100644 (file)
@@ -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',
index 1642b79ddffd35d2b43ee41c0ecfeeeef7b0cd59..ec6e15cfa71d162f39980d4209252a694a36d042 100644 (file)
@@ -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');
index a0a28476edc2777c95dcf5b4accb80d980df820f..7bf3ae3918b36f22f85035220b9a5d5cd8c03608 100644 (file)
@@ -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;
       }
     }
   }
index 30188ce3bfc878e310754d5ea7c9735443d7147b..4fedbc69af6ce859784a43b37eecd8e8491b97ec 100644 (file)
@@ -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'],
index 3ed6d8f86235bb08c643aa066dedccf50c6022cb..0045346efebca8c2eac7207acb917adf95ed1dc1 100644 (file)
@@ -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"];
     }
index 5f4b74bd9bfe8014c3162ca12e8a51a91dc21c98..a3674146350a41aebea2a1b9f174dc0f7a2f3e52 100644 (file)
@@ -32,6 +32,7 @@ mailerJobSize
 mailerJobsMax
 maxFileSize
 max_attachments
+max_attachments_backend
 replyTo
 secondDegRelPermissions
 securityAlert
index 68d82a2367aebaf3d5bf0ad766f9341a7b99ff0a..033d69c18aacf8ba5ec8b7cdbf4cecc9207e4451 100644 (file)
@@ -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 (file)
index 0000000..c461e05
--- /dev/null
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * Class CRM_Utils_Mail_EmailProcessorInboundTest
+ * @group headless
+ */
+class CRM_Utils_Mail_EmailProcessorInboundTest extends CiviUnitTestCase {
+
+  /**
+   * MailSettings record id.
+   *
+   * @var int
+   */
+  protected $mailSettingsId;
+
+  public function setUp() {
+    parent::setUp();
+    CRM_Utils_File::cleanDir(__DIR__ . '/data/mail');
+    mkdir(__DIR__ . '/data/mail');
+    // Note this is configured for Inbound Email Processing (not bounces)
+    // but otherwise is the same as bounces.
+    $this->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 (file)
index 0000000..6ee592f
--- /dev/null
@@ -0,0 +1,123 @@
+MIME-Version: 1.0\r
+Date: %%the_date%%\r
+Message-ID: <CAL+tpUmRn2J7zcjUAv4WXMTJZcEV67V2_Ktu77Ha_MGmth0CmQ@mail.gmail.com>\r
+Subject: Testing 4 attachments\r
+From: %%from_name%% <%%from_email%%>\r
+To: %%to_email%%\r
+Content-Type: multipart/mixed; boundary="000000000000a2ad5f059325db35"\r
+\r
+--000000000000a2ad5f059325db35\r
+Content-Type: multipart/alternative; boundary="000000000000a2ad5a059325db33"\r
+\r
+--000000000000a2ad5a059325db33\r
+Content-Type: text/plain; charset="UTF-8"\r
+\r
+test\r
+\r
+--000000000000a2ad5a059325db33\r
+Content-Type: text/html; charset="UTF-8"\r
+\r
+<div dir="ltr">test<br></div>\r
+\r
+--000000000000a2ad5a059325db33--\r
+--000000000000a2ad5f059325db35\r
+Content-Type: image/gif; name="c.gif"\r
+Content-Disposition: attachment; filename="c.gif"\r
+Content-Transfer-Encoding: base64\r
+X-Attachment-Id: f_k0v4qdju2\r
+Content-ID: <f_k0v4qdju2>\r
+\r
+R0lGODlhPwA+APcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBV\r
+ZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDV\r
+mQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMr\r
+zDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq\r
+/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2Yr\r
+AGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaq\r
+M2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kA\r
+ZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmA\r
+mZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/\r
+zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV\r
+/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/\r
+AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9V\r
+M/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//V\r
+Zv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAA/AD4A\r
+AAjCAPcJHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuX\r
+IgHInAnT4MybOGvi3JmzJc+fNFcCHQpAJdCCP1HyVLjU5E6GT0tGhdpzZNWGV0NmXTgVZFesN62G\r
+jbi1Y1muY7WmfXh2Y9uEbzPGPTgXY92BXz/eFbjXYl6Ef/XODSz4bVOnh/EmRkx08cnGhKVClqmT\r
+aE2CSS9r3sy5s+fPoEOLHk26tOnTqFPXDAgAOw==\r
+--000000000000a2ad5f059325db35\r
+Content-Type: image/gif; name="b.gif"\r
+Content-Disposition: attachment; filename="b.gif"\r
+Content-Transfer-Encoding: base64\r
+X-Attachment-Id: f_k0v4qdjj1\r
+Content-ID: <f_k0v4qdjj1>\r
+\r
+R0lGODlhPwA+APcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBV\r
+ZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDV\r
+mQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMr\r
+zDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq\r
+/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2Yr\r
+AGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaq\r
+M2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kA\r
+ZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmA\r
+mZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/\r
+zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV\r
+/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/\r
+AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9V\r
+M/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//V\r
+Zv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAA/AD4A\r
+AAjCAPcJHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuX\r
+IgHInAnT4MybOGvi3JmzJc+fNFcCHQpAJdCCP1HyVLjU5E6GT0tGhdpzZNWGV0NmXTgVZFesN62G\r
+jbi1Y1muY7WmfXh2Y9uEbzPGPTgXY92BXz/eFbjXYl6Ef/XODSz4bVOnh/EmRkx08cnGhKVClqmT\r
+aE2CSS9r3sy5s+fPoEOLHk26tOnTqFPXDAgAOw==\r
+--000000000000a2ad5f059325db35\r
+Content-Type: image/gif; name="d.gif"\r
+Content-Disposition: attachment; filename="d.gif"\r
+Content-Transfer-Encoding: base64\r
+X-Attachment-Id: f_k0v4qdk73\r
+Content-ID: <f_k0v4qdk73>\r
+\r
+R0lGODlhPwA+APcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBV\r
+ZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDV\r
+mQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMr\r
+zDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq\r
+/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2Yr\r
+AGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaq\r
+M2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kA\r
+ZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmA\r
+mZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/\r
+zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV\r
+/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/\r
+AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9V\r
+M/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//V\r
+Zv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAA/AD4A\r
+AAjCAPcJHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuX\r
+IgHInAnT4MybOGvi3JmzJc+fNFcCHQpAJdCCP1HyVLjU5E6GT0tGhdpzZNWGV0NmXTgVZFesN62G\r
+jbi1Y1muY7WmfXh2Y9uEbzPGPTgXY92BXz/eFbjXYl6Ef/XODSz4bVOnh/EmRkx08cnGhKVClqmT\r
+aE2CSS9r3sy5s+fPoEOLHk26tOnTqFPXDAgAOw==\r
+--000000000000a2ad5f059325db35\r
+Content-Type: image/gif; name="a.gif"\r
+Content-Disposition: attachment; filename="a.gif"\r
+Content-Transfer-Encoding: base64\r
+X-Attachment-Id: f_k0v4qdiv0\r
+Content-ID: <f_k0v4qdiv0>\r
+\r
+R0lGODlhPwA+APcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBV\r
+ZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDV\r
+mQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMr\r
+zDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq\r
+/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2Yr\r
+AGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaq\r
+M2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kA\r
+ZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmA\r
+mZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/\r
+zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV\r
+/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/\r
+AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9V\r
+M/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//V\r
+Zv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAA/AD4A\r
+AAjCAPcJHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuX\r
+IgHInAnT4MybOGvi3JmzJc+fNFcCHQpAJdCCP1HyVLjU5E6GT0tGhdpzZNWGV0NmXTgVZFesN62G\r
+jbi1Y1muY7WmfXh2Y9uEbzPGPTgXY92BXz/eFbjXYl6Ef/XODSz4bVOnh/EmRkx08cnGhKVClqmT\r
+aE2CSS9r3sy5s+fPoEOLHk26tOnTqFPXDAgAOw==\r
+--000000000000a2ad5f059325db35--\r