[Embed] Added support for inline images

This commit is contained in:
Miguel Dantas 2019-08-19 01:40:31 +01:00 committed by Diogo Peralta Cordeiro
parent bf885fbef8
commit 9ab13191af

View File

@ -127,11 +127,21 @@ class EmbedPlugin extends Plugin
$metadata->html = common_purify($info->description);
$metadata->type = $info->type;
$metadata->url = $info->url;
$metadata->thumbnail_url = $info->image;
$metadata->thumbnail_height = $info->imageHeight;
$metadata->thumbnail_width = $info->imageWidth;
if (substr($info->image, 0, 4) === 'data') {
// Inline image
$img_data = base64_decode(substr($info->image, stripos($info->image, 'base64,') + 7));
list($filename, $_, $_) = $this->validateAndWriteImage($img_data);
// Use a file URI for images, as file_embed can't store a filename
$metadata->thumbnail_url = 'file://' . File_thumbnail::path($filename);
} else {
$metadata->thumbnail_url = $info->image;
}
} catch (Exception $e) {
common_log(LOG_INFO, "Failed to find Embed data for {$url} with 'oscarotero/Embed'");
common_log(LOG_INFO, "Failed to find Embed data for {$url} with 'oscarotero/Embed'" .
", got exception: " . get_class($e));
}
if (isset($metadata->thumbnail_url)) {
@ -498,6 +508,79 @@ class EmbedPlugin extends Plugin
return !empty($headers['content-type']) && common_get_mime_media($headers['content-type']) === 'image';
}
/**
* Validate that $img_data is a valid image before writing it to
* disk, as well as resizing it to at most $this->thumbnail_width
* by $this->thumbnail_height
*
* @param string $img_data - The image data to validate. Taken by reference to avoid copying
* @param string|null $url - The url where the image came from, to fetch metadata
* @param array|null $headers - The headers possible previous request to $url
* @param int|null $file_id - The id of the file this image belongs to, used for logging
*/
protected function validateAndWriteImage(string &$img_data, ?string $url = null, ?string $headers = null, ?int $file_id = 0) : array
{
$info = @getimagesizefromstring($img_data);
// array indexes documented on php.net:
// https://php.net/manual/en/function.getimagesize.php
if ($info === false) {
throw new UnsupportedMediaException(_('Remote file format was not identified as an image.'), $url);
} elseif (!$info[0] || !$info[1]) {
throw new UnsupportedMediaException(_('Image file had impossible geometry (0 width or height)'));
}
$width = min($info[0], $this->thumbnail_width);
$height = min($info[1], $this->thumbnail_height);
$filehash = hash(File::FILEHASH_ALG, $img_data);
try {
if (!empty($url)) {
$original_name = HTTPClient::get_filename($url, $headers);
}
$filename = MediaFile::encodeFilename($original_name ?? '', $filehash);
$fullpath = File_thumbnail::path($filename);
// Write the file to disk. Throw Exception on failure
if (!file_exists($fullpath)) {
if (strpos($fullpath, INSTALLDIR) !== 0 || file_put_contents($fullpath, $img_data) === false) {
throw new ServerException(_('Could not write downloaded file to disk.'));
}
if (common_get_mime_media(MediaFile::getUploadedMimeType($fullpath)) !== 'image') {
@unlink($fullpath);
throw new UnsupportedMediaException(
_('Remote file format was not identified as an image.'),
$url
);
}
// If the image is not of the desired size, resize it
if ($info[0] > $this->thumbnail_width || $info[1] > $this->thumbnail_height) {
// Temporary object, not stored in DB
$img = new ImageFile(-1, $fullpath);
$box = $img->scaleToFit($this->thumbnail_width, $this->thumbnail_height, $this->thumbnail_crop);
$outpath = $img->resizeTo($fullpath, $box);
$filename = basename($outpath);
if ($fullpath !== $outpath) {
@unlink($fullpath);
}
}
} else {
throw new AlreadyFulfilledException('A thumbnail seems to already exist for remote file' .
($file_id ? 'with id==' . $file_id : '') . ' at path ' . $fullpath);
}
} catch (AlreadyFulfilledException $e) {
// Carry on
} catch (Exception $err) {
common_log(LOG_ERR, "Went to write a thumbnail to disk in EmbedPlugin::storeRemoteThumbnail " .
"but encountered error: {$err}");
throw $err;
} finally {
unset($img_data);
}
return [$filename, $width, $height];
}
/**
* Function to create and store a thumbnail representation of a remote image
*
@ -514,8 +597,15 @@ class EmbedPlugin extends Plugin
}
$url = $thumbnail->getUrl();
$this->checkWhitelist($url);
if (substr($url, 0, 7) == 'file://') {
$filename = substr($url, 7);
$info = getimagesize($filename);
$filename = basename($filename);
$width = $info[0];
$height = $info[1];
} else {
$this->checkWhitelist($url);
$head = (new HTTPClient())->head($url);
$headers = $head->getHeader();
$headers = array_change_key_case($headers, CASE_LOWER);
@ -543,61 +633,10 @@ class EmbedPlugin extends Plugin
$thumbnail->file_id,
$url
));
$imgData = HTTPClient::quickGet($url);
$info = @getimagesizefromstring($imgData);
// array indexes documented on php.net:
// https://php.net/manual/en/function.getimagesize.php
if ($info === false) {
throw new UnsupportedMediaException(_('Remote file format was not identified as an image.'), $url);
} elseif (!$info[0] || !$info[1]) {
throw new UnsupportedMediaException(_('Image file had impossible geometry (0 width or height)'));
}
$img_data = HTTPClient::quickGet($url);
$filehash = hash(File::FILEHASH_ALG, $imgData);
$width = min($info[0], $this->thumbnail_width);
$height = min($info[1], $this->thumbnail_height);
try {
$original_name = HTTPClient::get_filename($url, $headers);
$filename = MediaFile::encodeFilename($original_name, $filehash);
$fullpath = File_thumbnail::path($filename);
// Write the file to disk. Throw Exception on failure
if (!file_exists($fullpath)) {
if (strpos($fullpath, INSTALLDIR) !== 0 || file_put_contents($fullpath, $imgData) === false) {
throw new ServerException(_('Could not write downloaded file to disk.'));
}
if (common_get_mime_media(MediaFile::getUploadedMimeType($fullpath)) !== 'image') {
@unlink($fullpath);
throw new UnsupportedMediaException(
_('Remote file format was not identified as an image.'),
$url
);
}
// If the image is not of the desired size, resize it
if ($info[0] > $this->thumbnail_width || $info[1] > $this->thumbnail_height) {
// Temporary object, not stored in DB
$img = new ImageFile(-1, $fullpath);
$box = $img->scaleToFit($this->thumbnail_width, $this->thumbnail_height, $this->thumbnail_crop);
$outpath = $img->resizeTo($fullpath, $box);
$filename = basename($outpath);
if ($fullpath !== $outpath) {
@unlink($fullpath);
}
}
} else {
throw new AlreadyFulfilledException('A thumbnail seems to already exist for remote file with id==' .
$thumbnail->file_id . ' at path ' . $fullpath);
}
} catch (AlreadyFulfilledException $e) {
// Carry on
} catch (Exception $err) {
common_log(LOG_ERR, "Went to write a thumbnail to disk in EmbedPlugin::storeRemoteThumbnail " .
"but encountered error: {$err}");
return $err;
} finally {
unset($imgData);
list($filename, $width, $height) = $this->validateAndWriteImage($img_data, $url, $headers,
$thumbnail->file_id);
}
try {