Merge branch '2024.09-rc' into merge-2024.09-rc-into-develop

This commit is contained in:
Art4 2024-12-16 11:06:45 +01:00
commit c6c29b29b1
20 changed files with 554 additions and 146 deletions

View file

@ -2321,7 +2321,7 @@ class Contact
if ($fetchResult->isSuccess() && !empty($img_str)) {
$image = new Image($img_str, $fetchResult->getContentType(), $avatar);
if ($image->isValid()) {
$update_fields['blurhash'] = $image->getBlurHash();
$update_fields['blurhash'] = $image->getBlurHash($img_str);
} else {
return;
}

View file

@ -86,7 +86,7 @@ class Item
// Field list that is used to display the items
const DISPLAY_FIELDLIST = [
'uid', 'id', 'parent', 'guid', 'network', 'gravity',
'uid', 'id', 'parent', 'guid', 'network', 'protocol', 'gravity',
'uri-id', 'uri', 'thr-parent-id', 'thr-parent', 'parent-uri-id', 'parent-uri', 'conversation',
'commented', 'created', 'edited', 'received', 'verb', 'object-type', 'postopts', 'plink',
'wall', 'private', 'starred', 'origin', 'parent-origin', 'title', 'body', 'language', 'sensitive',
@ -4174,10 +4174,11 @@ class Item
* @param string $uri
* @param int $uid
* @param int $completion
* @param string $mimetype
*
* @return integer item id
*/
public static function fetchByLink(string $uri, int $uid = 0, int $completion = ActivityPub\Receiver::COMPLETION_MANUAL): int
public static function fetchByLink(string $uri, int $uid = 0, int $completion = ActivityPub\Receiver::COMPLETION_MANUAL, string $mimetype = ''): int
{
Logger::info('Trying to fetch link', ['uid' => $uid, 'uri' => $uri]);
$item_id = self::searchByLink($uri, $uid);
@ -4199,35 +4200,49 @@ class Item
Hook::callAll('item_by_link', $hookData);
if (isset($hookData['item_id'])) {
Logger::info('Hook link fetched', ['uid' => $uid, 'uri' => $uri, 'id' => $hookData['item_id']]);
return is_numeric($hookData['item_id']) ? $hookData['item_id'] : 0;
}
try {
$curlResult = DI::httpClient()->head($uri, [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::JSON_AS, HttpClientOptions::REQUEST => HttpClientRequest::ACTIVITYPUB]);
if (!HTTPSignature::isValidContentType($curlResult->getContentType(), $uri) && (current(explode(';', $curlResult->getContentType())) == 'application/json')) {
if (!$mimetype) {
try {
$curlResult = DI::httpClient()->head($uri, [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::JSON_AS, HttpClientOptions::REQUEST => HttpClientRequest::ACTIVITYPUB]);
$mimetype = empty($curlResult) ? '' : $curlResult->getContentType();
} catch (\Throwable $th) {
Logger::info('Error while fetching HTTP link via HEAD', ['uid' => $uid, 'uri' => $uri, 'code' => $th->getCode(), 'message' => $th->getMessage()]);
return 0;
}
}
if (!HTTPSignature::isValidContentType($mimetype, $uri) && (current(explode(';', $mimetype)) == 'application/json')) {
try {
// Issue 14126: Workaround for Mastodon servers that return "application/json" on a "head" request.
$curlResult = HTTPSignature::fetchRaw($uri, $uid);
$mimetype = empty($curlResult) ? '' : $curlResult->getContentType();
} catch (\Throwable $th) {
Logger::info('Error while fetching HTTP link via signed GET', ['uid' => $uid, 'uri' => $uri, 'code' => $th->getCode(), 'message' => $th->getMessage()]);
return 0;
}
if (HTTPSignature::isValidContentType($curlResult->getContentType(), $uri)) {
$fetched_uri = ActivityPub\Processor::fetchMissingActivity($uri, [], '', $completion, $uid);
}
} catch (\Throwable $th) {
Logger::info('Invalid link', ['uid' => $uid, 'uri' => $uri, 'code' => $th->getCode(), 'message' => $th->getMessage()]);
return 0;
}
if (!empty($fetched_uri)) {
$item_id = self::searchByLink($fetched_uri, $uid);
} else {
$item_id = Diaspora::fetchByURL($uri);
if (HTTPSignature::isValidContentType($mimetype, $uri)) {
$fetched_uri = ActivityPub\Processor::fetchMissingActivity($uri, [], '', $completion, $uid);
if (!empty($fetched_uri)) {
$item_id = self::searchByLink($fetched_uri, $uid);
if ($item_id) {
Logger::info('ActivityPub link fetched', ['uid' => $uid, 'uri' => $uri, 'id' => $item_id]);
return $item_id;
}
}
}
if (!empty($item_id)) {
Logger::info('Link fetched', ['uid' => $uid, 'uri' => $uri, 'id' => $item_id]);
$item_id = Diaspora::fetchByURL($uri);
if ($item_id) {
Logger::info('Diaspora link fetched', ['uid' => $uid, 'uri' => $uri, 'id' => $item_id]);
return $item_id;
}
Logger::info('Link not found', ['uid' => $uid, 'uri' => $uri]);
Logger::info('This is not an item link', ['uid' => $uid, 'uri' => $uri]);
return 0;
}

View file

@ -434,6 +434,7 @@ class Photo
$data = '';
$backend_ref = '';
$storage = '';
$img_str = $image->asString();
try {
if (DBA::isResult($existing_photo)) {
@ -442,9 +443,9 @@ class Photo
} else {
$storage = DI::storage();
}
$backend_ref = $storage->put($image->asString(), $backend_ref);
$backend_ref = $storage->put($img_str, $backend_ref);
} catch (InvalidClassStorageException $storageException) {
$data = $image->asString();
$data = $img_str;
}
$fields = [
@ -452,7 +453,7 @@ class Photo
'contact-id' => $cid,
'guid' => $guid,
'resource-id' => $rid,
'hash' => md5($image->asString()),
'hash' => md5($img_str),
'created' => $created,
'edited' => DateTimeFormat::utcNow(),
'filename' => basename($filename),
@ -460,8 +461,8 @@ class Photo
'album' => $album,
'height' => $image->getHeight(),
'width' => $image->getWidth(),
'datasize' => strlen($image->asString()),
'blurhash' => $image->getBlurHash(),
'datasize' => strlen($img_str),
'blurhash' => $image->getBlurHash($img_str),
'data' => $data,
'scale' => $scale,
'photo-type' => $type,

View file

@ -127,12 +127,12 @@ class Link
if (Images::isSupportedMimeType($fields['mimetype'])) {
$img_str = $curlResult->getBodyString();
$image = new Image($img_str, $fields['mimetype'], $url);
$image = new Image($img_str, $fields['mimetype'], $url, false);
if ($image->isValid()) {
$fields['mimetype'] = $image->getType();
$fields['width'] = $image->getWidth();
$fields['height'] = $image->getHeight();
$fields['blurhash'] = $image->getBlurHash();
$fields['blurhash'] = $image->getBlurHash($img_str);
}
}

View file

@ -51,6 +51,8 @@ class Media
const ACTIVITY = 20;
const ACCOUNT = 21;
const HLS = 22;
const JSON = 23;
const LD = 24;
const DOCUMENT = 128;
/**
@ -180,14 +182,14 @@ class Media
}
// Fetch the mimetype or size if missing.
if (Network::isValidHttpUrl($media['url']) && (empty($media['mimetype']) || empty($media['size']))) {
if (Network::isValidHttpUrl($media['url']) && empty($media['mimetype']) && !in_array($media['type'], [self::IMAGE, self::HLS])) {
$timeout = DI::config()->get('system', 'xrd_timeout');
try {
$curlResult = DI::httpClient()->head($media['url'], [HttpClientOptions::TIMEOUT => $timeout, HttpClientOptions::REQUEST => HttpClientRequest::CONTENTTYPE]);
$curlResult = DI::httpClient()->head($media['url'], [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::AS_DEFAULT, HttpClientOptions::TIMEOUT => $timeout, HttpClientOptions::REQUEST => HttpClientRequest::CONTENTTYPE]);
// Workaround for systems that can't handle a HEAD request
if (!$curlResult->isSuccess() && ($curlResult->getReturnCode() == 405)) {
$curlResult = DI::httpClient()->get($media['url'], HttpClientAccept::DEFAULT, [HttpClientOptions::TIMEOUT => $timeout]);
if (!$curlResult->isSuccess() && in_array($curlResult->getReturnCode(), [400, 403, 405])) {
$curlResult = DI::httpClient()->get($media['url'], HttpClientAccept::AS_DEFAULT, [HttpClientOptions::TIMEOUT => $timeout]);
}
if ($curlResult->isSuccess()) {
if (empty($media['mimetype'])) {
@ -197,16 +199,20 @@ class Media
$media['size'] = (int)($curlResult->getHeader('Content-Length')[0] ?? strlen($curlResult->getBodyString() ?? ''));
}
} else {
Logger::notice('Could not fetch head', ['media' => $media]);
Logger::notice('Could not fetch head', ['media' => $media, 'code' => $curlResult->getReturnCode()]);
}
} catch (\Throwable $th) {
Logger::notice('Got exception', ['code' => $th->getCode(), 'message' => $th->getMessage()]);
}
}
$filetype = !empty($media['mimetype']) ? strtolower(current(explode('/', $media['mimetype']))) : '';
if (($media['type'] != self::DOCUMENT) && !empty($media['mimetype'])) {
$media = self::addType($media);
}
if (($media['type'] == self::IMAGE) || ($filetype == 'image')) {
Logger::debug('Got type for url', ['type' => $media['type'], 'mimetype' => $media['mimetype'] ?? '', 'url' => $media['url']]);
if ($media['type'] == self::IMAGE) {
$imagedata = Images::getInfoFromURLCached($media['url'], empty($media['description']));
if ($imagedata) {
$media['mimetype'] = $imagedata['mime'];
@ -223,23 +229,19 @@ class Media
}
}
if ($media['type'] != self::DOCUMENT) {
$media = self::addType($media);
}
if (!empty($media['preview'])) {
$media = self::addPreviewData($media);
}
if (in_array($media['type'], [self::TEXT, self::APPLICATION, self::HTML, self::XML, self::PLAIN])) {
$media = self::addActivity($media);
}
if (in_array($media['type'], [self::TEXT, self::APPLICATION, self::HTML, self::XML, self::PLAIN])) {
if (in_array($media['type'], [self::TEXT, self::ACTIVITY, self::LD, self::JSON, self::HTML, self::XML, self::PLAIN])) {
$media = self::addAccount($media);
}
if ($media['type'] == self::HTML) {
if (in_array($media['type'], [self::ACTIVITY, self::LD, self::JSON])) {
$media = self::addActivity($media);
}
if (in_array($media['type'], [self::HTML, self::LD, self::JSON])) {
$media = self::addPage($media);
}
@ -254,6 +256,16 @@ class Media
$imagedata = Images::getInfoFromURLCached($media['preview']);
if ($imagedata) {
$media['blurhash'] = $imagedata['blurhash'] ?? null;
// When the original picture is potentially animated but the preview isn't, we override the preview
if (in_array($media['mimetype'] ?? '', ['image/gif', 'image/png']) && !in_array($imagedata['mime'], ['image/gif', 'image/png'])) {
$media['preview'] = $media['url'];
$media['preview-width'] = $media['width'];
$media['preview-height'] = $media['height'];
return $media;
}
$media['preview-width'] = $imagedata[0];
$media['preview-height'] = $imagedata[1];
}
@ -269,19 +281,22 @@ class Media
*/
private static function addActivity(array $media): array
{
$id = Item::fetchByLink($media['url'], 0, ActivityPub\Receiver::COMPLETION_ASYNC);
$id = Item::fetchByLink($media['url'], 0, ActivityPub\Receiver::COMPLETION_ASYNC, $media['mimetype'] ?? '');
if (empty($id)) {
$media['type'] = $media['type'] == self::ACTIVITY ? self::JSON : $media['type'];
return $media;
}
$item = Post::selectFirst([], ['id' => $id, 'network' => Protocol::FEDERATED]);
if (empty($item['id'])) {
Logger::debug('Not a federated activity', ['id' => $id, 'uri-id' => $media['uri-id'], 'url' => $media['url']]);
$media['type'] = $media['type'] == self::ACTIVITY ? self::JSON : $media['type'];
return $media;
}
if ($item['uri-id'] == $media['uri-id']) {
Logger::info('Media-Uri-Id is identical to Uri-Id', ['uri-id' => $media['uri-id']]);
$media['type'] = $media['type'] == self::ACTIVITY ? self::JSON : $media['type'];
return $media;
}
@ -290,6 +305,7 @@ class Media
parse_url($item['plink'], PHP_URL_HOST) != parse_url($item['uri'], PHP_URL_HOST)
) {
Logger::debug('Not a link to an activity', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'plink' => $item['plink'], 'uri' => $item['uri']]);
$media['type'] = $media['type'] == self::ACTIVITY ? self::JSON : $media['type'];
return $media;
}
@ -375,14 +391,23 @@ class Media
*/
private static function addPage(array $media): array
{
$data = ParseUrl::getSiteinfoCached($media['url']);
$data = ParseUrl::getSiteinfoCached($media['url'], $media['mimetype'] ?? '');
if (empty($data['images'][0]['src']) && empty($data['text']) && empty($data['title'])) {
if (!empty($media['preview'])) {
$media = self::addPreviewData($media);
Logger::debug('Detected site data is empty, use suggested media data instead', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'type' => $data['type']]);
}
} else {
$media['preview'] = $data['images'][0]['src'] ?? null;
$media['preview-height'] = $data['images'][0]['height'] ?? null;
$media['preview-width'] = $data['images'][0]['width'] ?? null;
$media['blurhash'] = $data['images'][0]['blurhash'] ?? null;
$media['description'] = $data['text'] ?? null;
$media['name'] = $data['title'] ?? null;
}
$media['type'] = self::HTML;
$media['size'] = $data['size'] ?? null;
$media['preview'] = $data['images'][0]['src'] ?? null;
$media['preview-height'] = $data['images'][0]['height'] ?? null;
$media['preview-width'] = $data['images'][0]['width'] ?? null;
$media['blurhash'] = $data['images'][0]['blurhash'] ?? null;
$media['description'] = $data['text'] ?? null;
$media['name'] = $data['title'] ?? null;
$media['author-url'] = $data['author_url'] ?? null;
$media['author-name'] = $data['author_name'] ?? null;
$media['author-image'] = $data['author_img'] ?? null;
@ -481,6 +506,12 @@ class Media
$type = self::TORRENT;
} elseif (($filetype == 'application') && ($subtype == 'vnd.apple.mpegurl')) {
$type = self::HLS;
} elseif (($filetype == 'application') && ($subtype == 'activity+json')) {
$type = self::ACTIVITY;
} elseif (($filetype == 'application') && ($subtype == 'ld+json')) {
$type = self::LD;
} elseif (($filetype == 'application') && ($subtype == 'json')) {
$type = self::JSON;
} elseif ($filetype == 'application') {
$type = self::APPLICATION;
} else {