diff --git a/app/Http/Controllers/ConversationsController.php b/app/Http/Controllers/ConversationsController.php
index b40e12ba..691205dc 100755
--- a/app/Http/Controllers/ConversationsController.php
+++ b/app/Http/Controllers/ConversationsController.php
@@ -753,6 +753,13 @@ class ConversationsController extends Controller
}
}
+ $body = $request->body;
+
+ // Replace base64 images with attachment URLs in case text
+ // was copy and pasted into the editor.
+ // https://github.com/freescout-helpdesk/freescout/issues/3057
+ $body = Thread::replaceBase64ImagesWithAttachments($body);
+
// List of emails.
$to_array = [];
if ($is_forward) {
@@ -789,7 +796,7 @@ class ConversationsController extends Controller
$conversation = new Conversation();
$conversation->type = $type;
$conversation->subject = $request->subject;
- $conversation->setPreview($request->body);
+ $conversation->setPreview($body);
$conversation->mailbox_id = $request->mailbox_id;
$conversation->created_by_user_id = auth()->user()->id;
$conversation->source_via = Conversation::PERSON_USER;
@@ -937,7 +944,7 @@ class ConversationsController extends Controller
$conversation->threads_count++;
// We need to set preview here as when conversation is created from draft,
// ThreadObserver::created() method is not called.
- $conversation->setPreview($request->body);
+ $conversation->setPreview($body);
}
$conversation->save();
@@ -993,7 +1000,7 @@ class ConversationsController extends Controller
$thread->created_by_user_id = auth()->user()->id;
$thread->edited_by_user_id = null;
$thread->edited_at = null;
- $thread->body = $request->body;
+ $thread->body = $body;
if ($is_create && !$is_multiple && count($to_array) > 1) {
$thread->setTo($to_array);
} else {
diff --git a/app/Misc/Helper.php b/app/Misc/Helper.php
index 5e278a8c..8c73058b 100644
--- a/app/Misc/Helper.php
+++ b/app/Misc/Helper.php
@@ -1331,7 +1331,7 @@ class Helper
$links = array();
// Extract existing links and tags
- $value = preg_replace_callback('~(.*?|<.*?>)~i', function ($match) use (&$links) { return '<' . array_push($links, $match[1]) . '>'; }, $value ?? '');
+ $value = preg_replace_callback('~(.*?|<.*?>)~i', function ($match) use (&$links) { return '<' . array_push($links, $match[1]) . '>'; }, $value ?? '') ?: $value;
$value = $value ?? '';
@@ -1339,14 +1339,17 @@ class Helper
foreach ((array)$protocols as $protocol) {
switch ($protocol) {
case 'http':
- case 'https': $value = preg_replace_callback('~(?:(https?)://([^\s<]+)|(www\.[^\s<]+?\.[^\s<]+))(?$protocol://$link") . '>'; }, $value); break;
- case 'mail': $value = preg_replace_callback('~([^\s<>]+?@[^\s<]+?\.[^\s<]+)(?{$match[1]}") . '>'; }, $value); break;
- default: $value = preg_replace_callback('~' . preg_quote($protocol, '~') . '://([^\s<]+?)(?$protocol://{$match[1]}") . '>'; }, $value); break;
+ case 'https': $value = preg_replace_callback('~(?:(https?)://([^\s<]+)|(www\.[^\s<]+?\.[^\s<]+))(?$protocol://$link") . '>'; }, $value) ?: $value;
+ break;
+ case 'mail': $value = preg_replace_callback('~([^\s<>]+?@[^\s<]+?\.[^\s<]+)(?{$match[1]}") . '>'; }, $value) ?: $value;
+ break;
+ default: $value = preg_replace_callback('~' . preg_quote($protocol, '~') . '://([^\s<]+?)(?$protocol://{$match[1]}") . '>'; }, $value) ?: $value;
+ break;
}
}
// Insert all link
- return preg_replace_callback('/<(\d+)>/', function ($match) use (&$links) { return $links[$match[1] - 1]; }, $value ?? '');
+ return preg_replace_callback('/<(\d+)>/', function ($match) use (&$links) { return $links[$match[1] - 1]; }, $value ?? '') ?: $value;
}
/**
@@ -1873,4 +1876,14 @@ class Helper
$then = mb_substr($string, 1, null, $encoding);
return mb_strtoupper($first_char, $encoding) . $then;
}
+
+ /**
+ * This is needed to allow using regexes for large texts.
+ */
+ public static function setPcreBacktrackLimit()
+ {
+ if ((int)ini_get('pcre.backtrack_limit') <= 1000000) {
+ ini_set('pcre.backtrack_limit', 1000000000);
+ }
+ }
}
diff --git a/app/Thread.php b/app/Thread.php
index 3940f16d..36811d1c 100644
--- a/app/Thread.php
+++ b/app/Thread.php
@@ -308,11 +308,12 @@ class Thread extends Model
// Change "background:" to "background-color:".
// https://github.com/freescout-helpdesk/freescout/issues/2560
- $body = preg_replace("/(<[^<>]+style=[\"'][^\"']*)background: *([^;() ]+;)/", '$1background-color:$2', $body);
+ // Keep in mind that with large texts preg_replace() may return null.
+ $body = preg_replace("/(<[^<>]+style=[\"'][^\"']*)background: *([^;() ]+;)/", '$1background-color:$2', $body) ?: $body;
// Cut out "collapse" class as it hides elements.
- $body = preg_replace("/(<[^<>\r\n]+class=([\"'][^\"']* |[\"']))(collapse|hidden)([\"' ])/", '$1$4', $body);
-
+ $body = preg_replace("/(<[^<>\r\n]+class=([\"'][^\"']* |[\"']))(collapse|hidden)([\"' ])/", '$1$4', $body) ?: $body;
+
return \Helper::purifyHtml($body);
}
@@ -352,7 +353,7 @@ class Thread extends Model
return sprintf('target="_blank" %s', array_shift($m2));
}, $tpl);
- }, $body);
+ }, $body) ?: $body;
return $body;
}
@@ -1440,4 +1441,36 @@ class Thread extends Model
{
return $this->type == self::TYPE_MESSAGE;
}
+
+ public static function replaceBase64ImagesWithAttachments($body, $user_id = null)
+ {
+ \Helper::setPcreBacktrackLimit();
+
+ $body = preg_replace_callback("#(]+src=[\"'])data:image/([^;]+);base64,([^\"']+)([\"'])#",
+ function ($match) {
+ $attachment = null;
+ $data = base64_decode($match[3]);
+
+ if ($data) {
+ $attachment = Attachment::create(
+ $file_name = number_format(microtime(true), 4, '', '').'.'.$match[2],
+ $mime_type = 'image/'.$match[2],
+ $type = Attachment::TYPE_IMAGE,
+ $data,
+ $uploaded_file = null,
+ $embedded = true,
+ $thread_id = null,
+ $user_id = \Auth::id()
+ );
+ }
+ if ($attachment) {
+ return $match[1].$attachment->url().$match[4];
+ } else {
+ return $match[0];
+ }
+ }, $body
+ );
+
+ return $body;
+ }
}