1
0
mirror of https://github.com/freescout-helpdesk/freescout.git synced 2024-11-24 03:12:46 +01:00

Replace base64 images in messages with attachment URLs - closes #3057

This commit is contained in:
FreeScout 2023-05-30 23:53:20 -07:00
parent ac76251b75
commit 0ef404f3ef
3 changed files with 65 additions and 12 deletions

View File

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

View File

@ -1331,7 +1331,7 @@ class Helper
$links = array();
// Extract existing links and tags
$value = preg_replace_callback('~(<a .*?>.*?</a>|<.*?>)~i', function ($match) use (&$links) { return '<' . array_push($links, $match[1]) . '>'; }, $value ?? '');
$value = preg_replace_callback('~(<a .*?>.*?</a>|<.*?>)~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<]+))(?<![\.,:])~i', function ($match) use ($protocol, &$links, $attr) { if ($match[1]) $protocol = $match[1]; $link = $match[2] ?: $match[3]; return '<' . array_push($links, "<a $attr href=\"$protocol://$link\">$protocol://$link</a>") . '>'; }, $value); break;
case 'mail': $value = preg_replace_callback('~([^\s<>]+?@[^\s<]+?\.[^\s<]+)(?<![\.,:])~', function ($match) use (&$links, $attr) { return '<' . array_push($links, "<a $attr href=\"mailto:{$match[1]}\">{$match[1]}</a>") . '>'; }, $value); break;
default: $value = preg_replace_callback('~' . preg_quote($protocol, '~') . '://([^\s<]+?)(?<![\.,:])~i', function ($match) use ($protocol, &$links, $attr) { return '<' . array_push($links, "<a $attr href=\"$protocol://{$match[1]}\">$protocol://{$match[1]}</a>") . '>'; }, $value); break;
case 'https': $value = preg_replace_callback('~(?:(https?)://([^\s<]+)|(www\.[^\s<]+?\.[^\s<]+))(?<![\.,:])~i', function ($match) use ($protocol, &$links, $attr) { if ($match[1]) $protocol = $match[1]; $link = $match[2] ?: $match[3]; return '<' . array_push($links, "<a $attr href=\"$protocol://$link\">$protocol://$link</a>") . '>'; }, $value) ?: $value;
break;
case 'mail': $value = preg_replace_callback('~([^\s<>]+?@[^\s<]+?\.[^\s<]+)(?<![\.,:])~', function ($match) use (&$links, $attr) { return '<' . array_push($links, "<a $attr href=\"mailto:{$match[1]}\">{$match[1]}</a>") . '>'; }, $value) ?: $value;
break;
default: $value = preg_replace_callback('~' . preg_quote($protocol, '~') . '://([^\s<]+?)(?<![\.,:])~i', function ($match) use ($protocol, &$links, $attr) { return '<' . array_push($links, "<a $attr href=\"$protocol://{$match[1]}\">$protocol://{$match[1]}</a>") . '>'; }, $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);
}
}
}

View File

@ -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("#(<img[^<>]+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;
}
}