diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index 09663ac7c2..f2c70f5452 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -174,7 +174,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction foreach (array_unique($matches[0]) as $match) { try { $this->media_ids[$match] = File::getByID($match); - } catch (EmptyIdException $e) { + } catch (EmptyPkeyValueException $e) { // got a zero from the client, at least Twidere does this on occasion } catch (NoResultException $e) { // File ID was not found. Do we abort and report to the client? diff --git a/classes/File.php b/classes/File.php index 1a38aefa72..b28f1373d6 100644 --- a/classes/File.php +++ b/classes/File.php @@ -120,7 +120,7 @@ class File extends Managed_DataObject // $args['attachment'] should always be set if action===attachment, given our routing rules $file = File::getByID($args['attachment']); return $file; - } catch (EmptyIdException $e) { + } catch (EmptyPkeyValueException $e) { // ...but $args['attachment'] can also be 0... } catch (NoResultException $e) { // apparently this link goes to us, but is _not_ an existing attachment (File) ID? diff --git a/classes/Managed_DataObject.php b/classes/Managed_DataObject.php index 5b22672d7b..8c9a55f2b6 100644 --- a/classes/Managed_DataObject.php +++ b/classes/Managed_DataObject.php @@ -383,14 +383,35 @@ abstract class Managed_DataObject extends Memcached_DataObject static function getByID($id) { + if (!property_exists(get_called_class(), 'id')) { + throw new ServerException('Trying to get undefined property of dataobject class.'); + } if (empty($id)) { - throw new EmptyIdException(get_called_class()); + throw new EmptyPkeyValueException(get_called_class(), 'id'); } // getByPK throws exception if id is null // or if the class does not have a single 'id' column as primary key return static::getByPK(array('id' => $id)); } + static function getByUri($uri) + { + if (!property_exists(get_called_class(), 'uri')) { + throw new ServerException('Trying to get undefined property of dataobject class.'); + } + if (empty($uri)) { + throw new EmptyPkeyValueException(get_called_class(), 'uri'); + } + + $class = get_called_class(); + $obj = new $class(); + $obj->uri = $uri; + if (!$obj->find(true)) { + throw new NoResultException($obj); + } + return $obj; + } + /** * Returns an ID, checked that it is set and reasonably valid * diff --git a/classes/Notice.php b/classes/Notice.php index c130cccc26..b828678d87 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -336,16 +336,6 @@ class Notice extends Managed_DataObject } } - public static function getByUri($uri) - { - $notice = new Notice(); - $notice->uri = $uri; - if (!$notice->find(true)) { - throw new NoResultException($notice); - } - return $notice; - } - /** * Extract #hashtags from this notice's content and save them to the database. */ @@ -2683,7 +2673,7 @@ class Notice extends Managed_DataObject public static function getAsTimestamp($id) { if (empty($id)) { - throw new EmptyIdException('Notice'); + throw new EmptyPkeyValueException('Notice', 'id'); } $timestamp = null; @@ -3091,6 +3081,44 @@ class Notice extends Managed_DataObject $schema = Schema::get(); $schemadef = $schema->getTableDef($table); + // 2015-09-04 We move Notice location data to Notice_location + // First we see if we have to do this at all + if (isset($schemadef['fields']['lat']) + && isset($schemadef['fields']['lon']) + && isset($schemadef['fields']['location_id']) + && isset($schemadef['fields']['location_ns'])) { + // Then we make sure the Notice_location table is created! + $schema->ensureTable('notice_location', Notice_location::schemaDef()); + + // Then we continue on our road to migration! + echo "\nFound old $table table, moving location data to 'notice_location' table... (this will probably take a LONG time, but can be aborted and continued)"; + + $notice = new Notice(); + $notice->query(sprintf('SELECT id, lat, lon, location_id, location_ns FROM %1$s ' . + 'WHERE lat IS NOT NULL ' . + 'OR lon IS NOT NULL ' . + 'OR location_id IS NOT NULL ' . + 'OR location_ns IS NOT NULL', + $schema->quoteIdentifier($table))); + print "\nFound {$notice->N} notices with location data, inserting"; + while ($notice->fetch()) { + $notloc = Notice_location::getKV('notice_id', $notice->id); + if ($notloc instanceof Notice_location) { + print "-"; + continue; + } + $notloc = new Notice_location(); + $notloc->notice_id = $notice->id; + $notloc->lat= $notice->lat; + $notloc->lon= $notice->lon; + $notloc->location_id= $notice->location_id; + $notloc->location_ns= $notice->location_ns; + $notloc->insert(); + print "."; + } + print "\n"; + } + /** * Make sure constraints are met before upgrading, if foreign keys * are not already in use. @@ -3163,45 +3191,5 @@ class Notice extends Managed_DataObject unset($notice); } } - - // 2015-09-04 We move Notice location data to Notice_location - // First we see if we have to do this at all - if (!isset($schemadef['fields']['lat']) - && !isset($schemadef['fields']['lon']) - && !isset($schemadef['fields']['location_id']) - && !isset($schemadef['fields']['location_ns'])) { - // We have already removed the location fields, so no need to migrate. - return; - } - // Then we make sure the Notice_location table is created! - $schema->ensureTable('notice_location', Notice_location::schemaDef()); - - // Then we continue on our road to migration! - echo "\nFound old $table table, moving location data to 'notice_location' table... (this will probably take a LONG time, but can be aborted and continued)"; - - $notice = new Notice(); - $notice->query(sprintf('SELECT id, lat, lon, location_id, location_ns FROM %1$s ' . - 'WHERE lat IS NOT NULL ' . - 'OR lon IS NOT NULL ' . - 'OR location_id IS NOT NULL ' . - 'OR location_ns IS NOT NULL', - $schema->quoteIdentifier($table))); - print "\nFound {$notice->N} notices with location data, inserting"; - while ($notice->fetch()) { - $notloc = Notice_location::getKV('notice_id', $notice->id); - if ($notloc instanceof Notice_location) { - print "-"; - continue; - } - $notloc = new Notice_location(); - $notloc->notice_id = $notice->id; - $notloc->lat= $notice->lat; - $notloc->lon= $notice->lon; - $notloc->location_id= $notice->location_id; - $notloc->location_ns= $notice->location_ns; - $notloc->insert(); - print "."; - } - print "\n"; } } diff --git a/classes/User.php b/classes/User.php index c9c61d3aed..952b74134b 100644 --- a/classes/User.php +++ b/classes/User.php @@ -140,16 +140,6 @@ class User extends Managed_DataObject return $this->uri; } - static function getByUri($uri) - { - $user = new User(); - $user->uri = $uri; - if (!$user->find(true)) { - throw new NoResultException($user); - } - return $user; - } - public function getNickname() { return $this->getProfile()->getNickname(); diff --git a/lib/default.php b/lib/default.php index 12e194f549..b3685a284c 100644 --- a/lib/default.php +++ b/lib/default.php @@ -64,6 +64,9 @@ $default = 'notice' => null, // site wide notice text 'build' => 1, // build number, for code-dependent cache ), + 'security' => + array('hash_algos' => ['sha1', 'sha256', 'sha512'], // set to null for anything that hash_hmac() can handle (and is in hash_algos()) + ), 'db' => array('database' => null, // must be set 'schema_location' => INSTALLDIR . '/classes', diff --git a/lib/emptyidexception.php b/lib/emptypkeyvalueexception.php similarity index 73% rename from lib/emptyidexception.php rename to lib/emptypkeyvalueexception.php index ddf781b067..4470b7ba31 100644 --- a/lib/emptyidexception.php +++ b/lib/emptypkeyvalueexception.php @@ -29,10 +29,13 @@ if (!defined('GNUSOCIAL')) { exit(1); } -class EmptyIdException extends ServerException +class EmptyPkeyValueException extends ServerException { - public function __construct($called_class) + public function __construct($called_class, $key=null) { - parent::__construct(sprintf(_('Empty ID value was given to query for a "%s" object'), $called_class)); + // FIXME: translate the 'not specified' case? + parent::__construct(sprintf(_('Empty primary key (%1$s) value was given to query for a "%2$s" object'), + is_null($key) ? 'not specified' : _ve($key), + $called_class)); } } diff --git a/plugins/Favorite/classes/Fave.php b/plugins/Favorite/classes/Fave.php index c9bb7a6dd2..fb5575c0d4 100644 --- a/plugins/Favorite/classes/Fave.php +++ b/plugins/Favorite/classes/Fave.php @@ -132,7 +132,7 @@ class Fave extends Managed_DataObject } catch (NoResultException $e) { // In case there's some inconsistency where the profile or notice was deleted without losing the fave db entry common_log(LOG_INFO, '"'.get_class($e->obj).'" with id=='.var_export($e->obj->id, true).' object not found when deleting favorite, ignoring...'); - } catch (EmptyIdException $e) { + } catch (EmptyPkeyValueException $e) { // Some buggy instances of GNU social have had favorites with notice id==0 stored in the database common_log(LOG_INFO, _ve($e->getMessage())); } diff --git a/plugins/LRDD/lib/discovery.php b/plugins/LRDD/lib/discovery.php index 6818e12557..03f24e04fa 100644 --- a/plugins/LRDD/lib/discovery.php +++ b/plugins/LRDD/lib/discovery.php @@ -122,9 +122,24 @@ class Discovery throw new Exception('Unexpected HTTP status code.'); } - $xrd->loadString($response->getBody()); + switch ($response->getHeader('content-type')) { + case self::JRD_MIMETYPE_OLD: + case self::JRD_MIMETYPE: + $type = 'json'; + break; + case self::XRD_MIMETYPE: + $type = 'xml'; + break; + default: + // fall back to letting XML_XRD auto-detect + common_debug('No recognized content-type header for resource descriptor body.'); + $type = null; + } + $xrd->loadString($response->getBody(), $type); return $xrd; + } catch (Exception $e) { + common_log(LOG_INFO, sprintf('%s: Failed for %s: %s', _ve($class), _ve($uri), _ve($e->getMessage()))); continue; } } diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index d455f1f478..3e9a0c58d6 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -1315,6 +1315,12 @@ class OStatusPlugin extends Plugin { if ($target->getObjectType() === ActivityObject::PERSON) { $this->addWebFingerPersonLinks($xrd, $target); + } elseif ($target->getObjectType() === ActivityObject::GROUP) { + $xrd->links[] = new XML_XRD_Element_Link(Discovery::UPDATESFROM, + common_local_url('ApiTimelineGroup', + array('id' => $target->getGroup()->getID(), 'format' => 'atom')), + 'application/atom+xml'); + } // Salmon diff --git a/plugins/OStatus/classes/FeedSub.php b/plugins/OStatus/classes/FeedSub.php index 72746e9b90..13a5439421 100644 --- a/plugins/OStatus/classes/FeedSub.php +++ b/plugins/OStatus/classes/FeedSub.php @@ -442,10 +442,10 @@ class FeedSub extends Managed_DataObject */ public function receive($post, $hmac) { - common_log(LOG_INFO, __METHOD__ . ": packet for \"" . $this->getUri() . "\"! $hmac $post"); + common_log(LOG_INFO, sprintf(__METHOD__.': packet for %s with HMAC %s', _ve($this->getUri()), _ve($hmac))); if (!in_array($this->sub_state, array('active', 'nohub'))) { - common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH for inactive feed " . $this->getUri() . " (in state '$this->sub_state')"); + common_log(LOG_ERR, sprintf(__METHOD__.': ignoring PuSH for inactive feed %s (in state %s)', _ve($this->getUri()), _ve($this->sub_state))); return; } @@ -480,7 +480,7 @@ class FeedSub extends Managed_DataObject * shared secret that was set up at subscription time. * * If we don't have a shared secret, there should be no signature. - * If we we do, our the calculated HMAC should match theirs. + * If we do, our calculated HMAC should match theirs. * * @param string $post raw XML source as POSTed to us * @param string $hmac X-Hub-Signature HTTP header value, or empty @@ -489,29 +489,36 @@ class FeedSub extends Managed_DataObject protected function validatePushSig($post, $hmac) { if ($this->secret) { - if (preg_match('/^sha1=([0-9a-fA-F]{40})$/', $hmac, $matches)) { - $their_hmac = strtolower($matches[1]); - $our_hmac = hash_hmac('sha1', $post, $this->secret); + // {3,16} because shortest hash algorithm name is 3 characters (md2,md4,md5) and longest + // is currently 11 characters, but we'll leave some margin in the end... + if (preg_match('/^([0-9a-zA-Z\-\,]{3,16})=([0-9a-fA-F]+)$/', $hmac, $matches)) { + $hash_algo = strtolower($matches[1]); + $their_hmac = strtolower($matches[2]); + common_debug(sprintf(__METHOD__ . ': PuSH from feed %s uses HMAC algorithm %s with value: %s', _ve($this->getUri()), _ve($hash_algo), _ve($their_hmac))); + + if (!in_array($hash_algo, hash_algos())) { + // We can't handle this at all, PHP doesn't recognize the algorithm name ('md5', 'sha1', 'sha256' etc: https://secure.php.net/manual/en/function.hash-algos.php) + common_log(LOG_ERR, sprintf(__METHOD__.': HMAC algorithm %s unsupported, not found in PHP hash_algos()', _ve($hash_algo))); + return false; + } elseif (!is_null(common_config('security', 'hash_algos')) && !in_array($hash_algo, common_config('security', 'hash_algos'))) { + // We _won't_ handle this because there is a list of accepted hash algorithms and this one is not in it. + common_log(LOG_ERR, sprintf(__METHOD__.': Whitelist for HMAC algorithms exist, but %s is not included.', _ve($hash_algo))); + return false; + } + + $our_hmac = hash_hmac($hash_algo, $post, $this->secret); if ($their_hmac === $our_hmac) { return true; } - if (common_config('feedsub', 'debug')) { - $tempfile = tempnam(sys_get_temp_dir(), 'feedsub-receive'); - if ($tempfile) { - file_put_contents($tempfile, $post); - } - common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac for feed " . $this->getUri() . " on $this->huburi; saved to $tempfile"); - } else { - common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac for feed " . $this->getUri() . " on $this->huburi"); - } + common_log(LOG_ERR, sprintf(__METHOD__.': ignoring PuSH with bad HMAC hash: got %s, expected %s for feed %s from hub %s', _ve($their_hmac), _ve($our_hmac), _ve($this->getUri()), _ve($this->huburi))); } else { - common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bogus HMAC '$hmac'"); + common_log(LOG_ERR, sprintf(__METHOD__.': ignoring PuSH with bogus HMAC==', _ve($hmac))); } } else { if (empty($hmac)) { return true; } else { - common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with unexpected HMAC '$hmac'"); + common_log(LOG_ERR, sprintf(__METHOD__.': ignoring PuSH with unexpected HMAC==%s', _ve($hmac))); } } return false; diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index 639fe663b2..0a0fd2e067 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -157,10 +157,10 @@ class Magicsig extends Managed_DataObject $keypair = $rsa->createKey($bits); $magicsig->privateKey = new \phpseclib\Crypt\RSA(); - $magicsig->privateKey->loadKey($keypair['privatekey']); + $magicsig->privateKey->load($keypair['privatekey']); $magicsig->publicKey = new \phpseclib\Crypt\RSA(); - $magicsig->publicKey->loadKey($keypair['publickey']); + $magicsig->publicKey->load($keypair['publickey']); $magicsig->insert(); // will do $this->keypair = $this->toString(true); $magicsig->importKeys(); // seems it's necessary to re-read keys from text keypair @@ -316,7 +316,7 @@ class Magicsig extends Managed_DataObject public function verify($signed_bytes, $signature) { $signature = self::base64_url_decode($signature); - return $this->publicKey->verify($signed_bytes, $signature); + return $this->publicKey->verify($signed_bytes, $signature, \phpseclib\Crypt\RSA::PADDING_PKCS1); } /** diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index d84611ec16..ad23542cfd 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -1842,22 +1842,28 @@ class Ostatus_profile extends Managed_DataObject { $orig = clone($this); - common_debug('URIFIX These identities both say they are each other: "'.$orig->uri.'" and "'.$profile_uri.'"'); + common_debug('URIFIX These identities both say they are each other: '._ve($orig->uri).' and '._ve($profile_uri)); $this->uri = $profile_uri; - if (array_key_exists('feedurl', $hints)) { - if (!empty($this->feeduri)) { - common_debug('URIFIX Changing FeedSub ['.$feedsub->id.'] feeduri "'.$feedsub->uri.'" to "'.$hints['feedurl']); - $feedsub = FeedSub::getKV('uri', $this->feeduri); + if (array_key_exists('feedurl', $hints) && common_valid_http_url($hints['feedurl'])) { + try { + $feedsub = FeedSub::getByUri($this->feeduri); + common_debug('URIFIX Changing FeedSub id==['._ve($feedsub->id).'] feeduri '._ve($feedsub->uri).' to '._ve($hints['feedurl'])); $feedorig = clone($feedsub); $feedsub->uri = $hints['feedurl']; $feedsub->updateWithKeys($feedorig); - } else { - common_debug('URIFIX Old Ostatus_profile did not have feedurl set, ensuring feed: '.$hints['feedurl']); + } catch (EmptyPkeyValueException $e) { + common_debug('URIFIX Old Ostatus_profile did not have feedurl set, ensuring new feedurl: '._ve($hints['feedurl'])); + FeedSub::ensureFeed($hints['feedurl']); + } catch (NoResultException $e) { + common_debug('URIFIX Missing FeedSub entry for the Ostatus_profile, ensuring new feedurl: '._ve($hints['feedurl'])); FeedSub::ensureFeed($hints['feedurl']); } $this->feeduri = $hints['feedurl']; + } elseif (array_key_exists('feedurl')) { + common_log(LOG_WARN, 'The feedurl hint we got was not a valid HTTP URL: '._ve($hints['feedurl'])); } + if (array_key_exists('salmon', $hints)) { common_debug('URIFIX Changing Ostatus_profile salmonuri from "'.$this->salmonuri.'" to "'.$hints['salmon'].'"'); $this->salmonuri = $hints['salmon']; diff --git a/plugins/OStatus/lib/salmonaction.php b/plugins/OStatus/lib/salmonaction.php index 3b9472eeb8..4d2144c300 100644 --- a/plugins/OStatus/lib/salmonaction.php +++ b/plugins/OStatus/lib/salmonaction.php @@ -31,6 +31,8 @@ class SalmonAction extends Action protected $oprofile = null; // Ostatus_profile of the actor protected $actor = null; // Profile object of the actor + var $format = 'text'; // error messages will be printed in plaintext + var $xml = null; var $activity = null; var $target = null; diff --git a/plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php b/plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php index 64c6355b2b..650f27c21f 100644 --- a/plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php +++ b/plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php @@ -18,7 +18,7 @@ class StoreRemoteMediaPlugin extends Plugin public $domain_blacklist = array(); public $check_blacklist = false; - public $max_image_bytes = 5242880; // 5MiB max image size by default + public $max_image_bytes = 10485760; // 10MiB max image size by default protected $imgData = array(); diff --git a/plugins/WebFinger/WebFingerPlugin.php b/plugins/WebFinger/WebFingerPlugin.php index b41db564c3..03a8c140b7 100644 --- a/plugins/WebFinger/WebFingerPlugin.php +++ b/plugins/WebFinger/WebFingerPlugin.php @@ -140,6 +140,20 @@ class WebFingerPlugin extends Plugin throw $e; } + try { + common_debug(__METHOD__.': Finding User_group URI for WebFinger lookup on resource=='._ve($resource)); + $group = new User_group(); + $group->whereAddIn('uri', array_keys($alt_urls), $group->columnType('uri')); + $group->limit(1); + if ($group->find(true)) { + $profile = $group->getProfile(); + } + unset($group); + } catch (Exception $e) { + common_log(LOG_ERR, get_class($e).': '._ve($e->getMessage())); + throw $e; + } + // User URI did not match, so let's try our alt_urls as Profile URL values if (!$profile instanceof Profile) { common_debug(__METHOD__.': Finding Profile URLs for WebFinger lookup on resource=='._ve($resource)); diff --git a/plugins/WebFinger/lib/webfingerresource/profile.php b/plugins/WebFinger/lib/webfingerresource/profile.php index 5bfbda0f29..273e08dc47 100644 --- a/plugins/WebFinger/lib/webfingerresource/profile.php +++ b/plugins/WebFinger/lib/webfingerresource/profile.php @@ -23,11 +23,14 @@ class WebFingerResource_Profile extends WebFingerResource { $aliases = array(); - try { - // Try to create an acct: URI if we're dealing with a profile - $aliases[] = $this->reconstructAcct(); - } catch (WebFingerReconstructionException $e) { - common_debug("WebFinger reconstruction for Profile failed (id={$this->object->id})"); + // only persons ("accounts" or "agents" actually) have acct: URIs + if ($this->object->isPerson()) { + try { + // Try to create an acct: URI if we're dealing with a profile + $aliases[] = $this->reconstructAcct(); + } catch (WebFingerReconstructionException $e) { + common_debug("WebFinger reconstruction for Profile failed (id={$this->object->getID()})"); + } } return array_merge($aliases, parent::getAliases()); @@ -40,10 +43,10 @@ class WebFingerResource_Profile extends WebFingerResource if (Event::handle('StartWebFingerReconstruction', array($this->object, &$acct))) { // TODO: getUri may not always give us the correct host on remote users? $host = parse_url($this->object->getUri(), PHP_URL_HOST); - if (empty($this->object->nickname) || empty($host)) { + if (empty($this->object->getNickname()) || empty($host)) { throw new WebFingerReconstructionException($this->object); } - $acct = mb_strtolower(sprintf('acct:%s@%s', $this->object->nickname, $host)); + $acct = mb_strtolower(sprintf('acct:%s@%s', $this->object->getNickname(), $host)); Event::handle('EndWebFingerReconstruction', array($this->object, &$acct)); } @@ -55,34 +58,45 @@ class WebFingerResource_Profile extends WebFingerResource { if (Event::handle('StartWebFingerProfileLinks', array($xrd, $this->object))) { - $xrd->links[] = new XML_XRD_Element_Link(self::PROFILEPAGE, - $this->object->getUrl(), 'text/html'); - - // XFN - $xrd->links[] = new XML_XRD_Element_Link('http://gmpg.org/xfn/11', - $this->object->getUrl(), 'text/html'); - // FOAF - $xrd->links[] = new XML_XRD_Element_Link('describedby', - common_local_url('foaf', - array('nickname' => $this->object->nickname)), - 'application/rdf+xml'); - - $link = new XML_XRD_Element_Link('http://apinamespace.org/atom', - common_local_url('ApiAtomService', - array('id' => $this->object->nickname)), - 'application/atomsvc+xml'); -// XML_XRD must implement changing properties first $link['http://apinamespace.org/atom/username'] = $this->object->nickname; - $xrd->links[] = clone $link; - if (common_config('site', 'fancy')) { $apiRoot = common_path('api/', true); } else { $apiRoot = common_path('index.php/api/', true); } - $link = new XML_XRD_Element_Link('http://apinamespace.org/twitter', $apiRoot); -// XML_XRD must implement changing properties first $link['http://apinamespace.org/twitter/username'] = $this->object->nickname; - $xrd->links[] = clone $link; + // Profile page, can give more metadata from Link header or HTML parsing + $xrd->links[] = new XML_XRD_Element_Link(self::PROFILEPAGE, + $this->object->getUrl(), 'text/html'); + + // XFN + $xrd->links[] = new XML_XRD_Element_Link('http://gmpg.org/xfn/11', + $this->object->getUrl(), 'text/html'); + if ($this->object->isPerson()) { + // FOAF for user + $xrd->links[] = new XML_XRD_Element_Link('describedby', + common_local_url('foaf', + array('nickname' => $this->object->getNickname())), + 'application/rdf+xml'); + + // nickname discovery for apps etc. + $link = new XML_XRD_Element_Link('http://apinamespace.org/atom', + common_local_url('ApiAtomService', + array('id' => $this->object->getNickname())), + 'application/atomsvc+xml'); + // XML_XRD must implement changing properties first $link['http://apinamespace.org/atom/username'] = $this->object->getNickname(); + $xrd->links[] = clone $link; + + $link = new XML_XRD_Element_Link('http://apinamespace.org/twitter', $apiRoot); + // XML_XRD must implement changing properties first $link['http://apinamespace.org/twitter/username'] = $this->object->getNickname(); + $xrd->links[] = clone $link; + + } elseif ($this->object->isGroup()) { + // FOAF for group + $xrd->links[] = new XML_XRD_Element_Link('describedby', + common_local_url('foafgroup', + array('nickname' => $this->object->getNickname())), + 'application/rdf+xml'); + } Event::handle('EndWebFingerProfileLinks', array($xrd, $this->object)); }