namespace Friendica\Model;
use Friendica\Content\Text\HTML;
+use Friendica\Core\Cache\Duration;
use Friendica\Core\Logger;
+use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Network\Probe;
use Friendica\Protocol\ActivityNamespace;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\Crypto;
-use Friendica\Util\Network;
-use Friendica\Util\JsonLD;
use Friendica\Util\DateTimeFormat;
-use Friendica\Util\Strings;
+use Friendica\Util\HTTPSignature;
+use Friendica\Util\JsonLD;
+use Friendica\Util\Network;
class APContact
{
* @param string $addr Address
* @return array webfinger data
*/
- public static function fetchWebfingerData(string $addr)
+ private static function fetchWebfingerData(string $addr)
{
$addr_parts = explode('@', $addr);
if (count($addr_parts) != 2) {
$url = $apcontact['url'];
}
- $data = ActivityPub::fetchContent($url);
- if (empty($data)) {
+ $curlResult = HTTPSignature::fetchRaw($url);
+ $failed = empty($curlResult) || empty($curlResult->getBody()) ||
+ (!$curlResult->isSuccess() && ($curlResult->getReturnCode() != 410));
+
+ if (!$failed) {
+ $data = json_decode($curlResult->getBody(), true);
+ $failed = empty($data) || !is_array($data);
+ }
+
+ if (!$failed && ($curlResult->getReturnCode() == 410)) {
+ $data = ['@context' => ActivityPub::CONTEXT, 'id' => $url, 'type' => 'Tombstone'];
+ }
+
+ if ($failed) {
+ self::markForArchival($fetched_contact ?: []);
return $fetched_contact;
}
$compacted = JsonLD::compact($data);
-
if (empty($compacted['@id'])) {
return $fetched_contact;
}
+ // Detect multiple fast repeating request to the same address
+ // See https://github.com/friendica/friendica/issues/9303
+ $cachekey = 'apcontact:getByURL:' . $url;
+ $result = DI::cache()->get($cachekey);
+ if (!is_null($result)) {
+ Logger::notice('Multiple requests for the address', ['url' => $url, 'update' => $update, 'callstack' => System::callstack(20), 'result' => $result]);
+ } else {
+ DI::cache()->set($cachekey, System::callstack(20), Duration::FIVE_MINUTES);
+ }
+
$apcontact['url'] = $compacted['@id'];
$apcontact['uuid'] = JsonLD::fetchElement($compacted, 'diaspora:guid', '@value');
$apcontact['type'] = str_replace('as:', '', JsonLD::fetchElement($compacted, '@type'));
}
// Quit if none of the basic values are set
- if (empty($apcontact['url']) || empty($apcontact['inbox']) || empty($apcontact['type'])) {
+ if (empty($apcontact['url']) || empty($apcontact['type']) || (($apcontact['type'] != 'Tombstone') && empty($apcontact['inbox']))) {
return $fetched_contact;
+ } elseif ($apcontact['type'] == 'Tombstone') {
+ // The "inbox" field must have a content
+ $apcontact['inbox'] = '';
}
// Quit if this doesn't seem to be an account at all
if (!empty($data['subscribe'])) {
$apcontact['subscribe'] = $data['subscribe'];
}
+ } else {
+ $apcontact['addr'] = null;
}
}
$apcontact['baseurl'] = null;
}
+ if (empty($apcontact['subscribe'])) {
+ $apcontact['subscribe'] = null;
+ }
+
if (!empty($apcontact['baseurl']) && empty($fetched_contact['gsid'])) {
$apcontact['gsid'] = GServer::getID($apcontact['baseurl']);
} elseif (!empty($fetched_contact['gsid'])) {
$apcontact['updated'] = DateTimeFormat::utcNow();
- DBA::update('apcontact', $apcontact, ['url' => $url], true);
-
// We delete the old entry when the URL is changed
- if (($url != $apcontact['url']) && DBA::exists('apcontact', ['url' => $url]) && DBA::exists('apcontact', ['url' => $apcontact['url']])) {
+ if ($url != $apcontact['url']) {
+ Logger::info('Delete changed profile url', ['old' => $url, 'new' => $apcontact['url']]);
DBA::delete('apcontact', ['url' => $url]);
}
+ if (DBA::exists('apcontact', ['url' => $apcontact['url']])) {
+ DBA::update('apcontact', $apcontact, ['url' => $apcontact['url']]);
+ } else {
+ DBA::replace('apcontact', $apcontact);
+ }
+
Logger::info('Updated profile', ['url' => $url]);
return $apcontact;
}
+ /**
+ * Mark the given AP Contact as "to archive"
+ *
+ * @param array $apcontact
+ * @return void
+ */
+ public static function markForArchival(array $apcontact)
+ {
+ if (!empty($apcontact['inbox'])) {
+ Logger::info('Set inbox status to failure', ['inbox' => $apcontact['inbox']]);
+ HTTPSignature::setInboxStatus($apcontact['inbox'], false);
+ }
+
+ if (!empty($apcontact['sharedinbox'])) {
+ // Check if there are any available inboxes
+ $available = DBA::exists('apcontact', ["`sharedinbox` = ? AnD `inbox` IN (SELECT `url` FROM `inbox-status` WHERE `success` > `failure`)",
+ $apcontact['sharedinbox']]);
+ if (!$available) {
+ // If all known personal inboxes are failing then set their shared inbox to failure as well
+ Logger::info('Set shared inbox status to failure', ['sharedinbox' => $apcontact['sharedinbox']]);
+ HTTPSignature::setInboxStatus($apcontact['sharedinbox'], false, true);
+ }
+ }
+ }
+
+ /**
+ * Unmark the given AP Contact as "to archive"
+ *
+ * @param array $apcontact
+ * @return void
+ */
+ public static function unmarkForArchival(array $apcontact)
+ {
+ if (!empty($apcontact['inbox'])) {
+ Logger::info('Set inbox status to success', ['inbox' => $apcontact['inbox']]);
+ HTTPSignature::setInboxStatus($apcontact['inbox'], true);
+ }
+ if (!empty($apcontact['sharedinbox'])) {
+ Logger::info('Set shared inbox status to success', ['sharedinbox' => $apcontact['sharedinbox']]);
+ HTTPSignature::setInboxStatus($apcontact['sharedinbox'], true, true);
+ }
+ }
+
/**
* Unarchive inboxes
*
- * @param string $url inbox url
+ * @param string $url inbox url
+ * @param boolean $shared Shared Inbox
*/
private static function unarchiveInbox($url, $shared)
{
return;
}
- $now = DateTimeFormat::utcNow();
-
- $fields = ['archive' => false, 'success' => $now, 'shared' => $shared];
-
- if (!DBA::exists('inbox-status', ['url' => $url])) {
- $fields = array_merge($fields, ['url' => $url, 'created' => $now]);
- DBA::insert('inbox-status', $fields);
- } else {
- DBA::update('inbox-status', $fields, ['url' => $url]);
- }
+ HTTPSignature::setInboxStatus($url, true, $shared);
}
}