diff options
author | Evan Prodromou <evan@status.net> | 2010-02-26 11:40:12 -0500 |
---|---|---|
committer | Evan Prodromou <evan@status.net> | 2010-02-26 11:40:12 -0500 |
commit | d9d1a77bb02227e6e79392959643404391df2ea0 (patch) | |
tree | 87ce0a41e5fed266f46af31469bdddf9c0fa3833 /plugins/OStatus | |
parent | 417f5d653fb2053cd6194900eba591d42ebfcb0d (diff) | |
parent | ab8bb4d79e5f267323440b8cee01458b393ce2d1 (diff) |
Merge branch 'testing' of git@gitorious.org:statusnet/mainline into testing
Diffstat (limited to 'plugins/OStatus')
-rw-r--r-- | plugins/OStatus/OStatusPlugin.php | 20 | ||||
-rw-r--r-- | plugins/OStatus/actions/hostmeta.php | 16 | ||||
-rw-r--r-- | plugins/OStatus/actions/ostatusinit.php | 6 | ||||
-rw-r--r-- | plugins/OStatus/actions/xrd.php (renamed from plugins/OStatus/actions/webfinger.php) | 14 | ||||
-rw-r--r-- | plugins/OStatus/classes/Magicsig.php | 3 | ||||
-rw-r--r-- | plugins/OStatus/classes/Ostatus_profile.php | 196 | ||||
-rw-r--r-- | plugins/OStatus/lib/discovery.php | 310 | ||||
-rw-r--r-- | plugins/OStatus/lib/magicenvelope.php | 23 | ||||
-rw-r--r-- | plugins/OStatus/lib/salmon.php | 8 | ||||
-rw-r--r-- | plugins/OStatus/lib/webfinger.php | 164 |
10 files changed, 429 insertions, 331 deletions
diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 7f75b7b2b..46f986682 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -43,8 +43,8 @@ class OStatusPlugin extends Plugin // Discovery actions $m->connect('.well-known/host-meta', array('action' => 'hostmeta')); - $m->connect('main/webfinger', - array('action' => 'webfinger')); + $m->connect('main/xrd', + array('action' => 'xrd')); $m->connect('main/ostatus', array('action' => 'ostatusinit')); $m->connect('main/ostatus?nickname=:nickname', @@ -103,6 +103,20 @@ class OStatusPlugin extends Plugin } /** + * Add a link header for LRDD Discovery + */ + function onStartShowHTML($action) + { + if ($action instanceof ShowstreamAction) { + $acct = 'acct:'. $action->profile->nickname .'@'. common_config('site', 'server'); + $url = common_local_url('xrd'); + $url.= '?uri='. $acct; + + header('Link: <'.$url.'>; rel="'. Discovery::LRDD_REL.'"; type="application/xrd+xml"'); + } + } + + /** * Set up a PuSH hub link to our internal link for canonical timeline * Atom feeds for users and groups. */ @@ -644,7 +658,7 @@ class OStatusPlugin extends Plugin function onStartUserGroupHomeUrl($group, &$url) { - return $this->onStartUserGroupPermalink($group, &$url); + return $this->onStartUserGroupPermalink($group, $url); } function onStartUserGroupPermalink($group, &$url) diff --git a/plugins/OStatus/actions/hostmeta.php b/plugins/OStatus/actions/hostmeta.php index 850b8a0fe..3d00b98ae 100644 --- a/plugins/OStatus/actions/hostmeta.php +++ b/plugins/OStatus/actions/hostmeta.php @@ -31,12 +31,18 @@ class HostMetaAction extends Action { parent::handle(); - $w = new Webfinger(); - - $domain = common_config('site', 'server'); - $url = common_local_url('webfinger'); + $url = common_local_url('xrd'); $url.= '?uri={uri}'; - print $w->getHostMeta($domain, $url); + + $xrd = new XRD(); + + $xrd = new XRD(); + $xrd->host = $domain; + $xrd->links[] = array('rel' => Discovery::LRDD_REL, + 'template' => $url, + 'title' => array('Resource Descriptor')); + + print $xrd->toXML(); } } diff --git a/plugins/OStatus/actions/ostatusinit.php b/plugins/OStatus/actions/ostatusinit.php index 3f2f6368f..8ba8dcdcc 100644 --- a/plugins/OStatus/actions/ostatusinit.php +++ b/plugins/OStatus/actions/ostatusinit.php @@ -131,9 +131,9 @@ class OStatusInitAction extends Action function connectWebfinger($acct) { - $w = new Webfinger; + $disco = new Discovery; - $result = $w->lookup($acct); + $result = $disco->lookup($acct); if (!$result) { $this->clientError(_m("Couldn't look up OStatus account profile.")); } @@ -144,7 +144,7 @@ class OStatusInitAction extends Action $user = User::staticGet('nickname', $this->nickname); $target_profile = common_local_url('userbyid', array('id' => $user->id)); - $url = $w->applyTemplate($link['template'], $target_profile); + $url = Discovery::applyTemplate($link['template'], $target_profile); common_log(LOG_INFO, "Sending remote subscriber $acct to $url"); common_redirect($url, 303); } diff --git a/plugins/OStatus/actions/webfinger.php b/plugins/OStatus/actions/xrd.php index e292ccec9..e6b694d61 100644 --- a/plugins/OStatus/actions/webfinger.php +++ b/plugins/OStatus/actions/xrd.php @@ -24,7 +24,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -class WebfingerAction extends Action +class XrdAction extends Action { public $uri; @@ -40,11 +40,11 @@ class WebfingerAction extends Action function handle() { - $acct = Webfinger::normalize($this->uri); + $acct = Discovery::normalize($this->uri); $xrd = new XRD(); - list($nick, $domain) = explode('@', urldecode($acct)); + list($nick, $domain) = explode('@', substr(urldecode($acct), 5)); $nick = common_canonical_nickname($nick); $this->user = User::staticGet('nickname', $nick); @@ -55,18 +55,18 @@ class WebfingerAction extends Action $xrd->subject = $this->uri; $xrd->alias[] = common_profile_url($nick); - $xrd->links[] = array('rel' => Webfinger::PROFILEPAGE, + $xrd->links[] = array('rel' => Discovery::PROFILEPAGE, 'type' => 'text/html', 'href' => common_profile_url($nick)); - $xrd->links[] = array('rel' => Webfinger::UPDATESFROM, + $xrd->links[] = array('rel' => Discovery::UPDATESFROM, 'href' => common_local_url('ApiTimelineUser', array('id' => $this->user->id, 'format' => 'atom')), 'type' => 'application/atom+xml'); // hCard - $xrd->links[] = array('rel' => Webfinger::HCARD, + $xrd->links[] = array('rel' => Discovery::HCARD, 'type' => 'text/html', 'href' => common_local_url('hcard', array('nickname' => $nick))); @@ -91,7 +91,7 @@ class WebfingerAction extends Action if (!$magickey) { // No keypair yet, let's generate one. $magickey = new Magicsig(); - $magickey->generate(); + $magickey->generate($this->user->id); } $xrd->links[] = array('rel' => Magicsig::PUBLICKEYREL, diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index 681aec184..02882d19b 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -90,7 +90,7 @@ class Magicsig extends Memcached_DataObject return parent::insert(); } - public function generate($key_length = 512) + public function generate($user_id, $key_length = 512) { PEAR::pushErrorHandling(PEAR_ERROR_RETURN); @@ -101,6 +101,7 @@ class Magicsig extends Memcached_DataObject $this->_rsa = new Crypt_RSA($params); PEAR::popErrorHandling(); + $this->user_id = $user_id; $this->insert(); } diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 75b4bef41..091056c54 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -150,27 +150,7 @@ class Ostatus_profile extends Memcached_DataObject function asActivityObject() { if ($this->isGroup()) { - $object = new ActivityObject(); - $object->type = 'http://activitystrea.ms/schema/1.0/group'; - $object->id = $this->uri; - $self = $this->localGroup(); - - // @fixme put a standard getAvatar() interface on groups too - if ($self->homepage_logo) { - $object->avatar = $self->homepage_logo; - $map = array('png' => 'image/png', - 'jpg' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'gif' => 'image/gif'); - $extension = pathinfo(parse_url($object->avatar, PHP_URL_PATH), PATHINFO_EXTENSION); - if (isset($map[$extension])) { - // @fixme this ain't used/saved yet - $object->avatarType = $map[$extension]; - } - } - - $object->link = $this->uri; // @fixme accurate? - return $object; + return ActivityObject::fromGroup($this->localGroup()); } else { return ActivityObject::fromProfile($this->localProfile()); } @@ -189,57 +169,13 @@ class Ostatus_profile extends Memcached_DataObject */ function asActivityNoun($element) { - $xs = new XMLStringer(true); - $avatarHref = Avatar::defaultImage(AVATAR_PROFILE_SIZE); - $avatarType = 'image/png'; if ($this->isGroup()) { - $type = 'http://activitystrea.ms/schema/1.0/group'; - $self = $this->localGroup(); - - // @fixme put a standard getAvatar() interface on groups too - if ($self->homepage_logo) { - $avatarHref = $self->homepage_logo; - $map = array('png' => 'image/png', - 'jpg' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'gif' => 'image/gif'); - $extension = pathinfo(parse_url($avatarHref, PHP_URL_PATH), PATHINFO_EXTENSION); - if (isset($map[$extension])) { - $avatarType = $map[$extension]; - } - } + $noun = ActivityObject::fromGroup($this->localGroup()); + return $noun->asString('activity:' . $element); } else { - $type = 'http://activitystrea.ms/schema/1.0/person'; - $self = $this->localProfile(); - $avatar = $self->getAvatar(AVATAR_PROFILE_SIZE); - if ($avatar) { - $avatarHref = $avatar->url; - $avatarType = $avatar->mediatype; - } + $noun = ActivityObject::fromProfile($this->localProfile()); + return $noun->asString('activity:' . $element); } - $xs->elementStart('activity:' . $element); - $xs->element( - 'activity:object-type', - null, - $type - ); - $xs->element( - 'id', - null, - $this->uri); // ? - $xs->element('title', null, $self->getBestName()); - - $xs->element( - 'link', array( - 'type' => $avatarType, - 'href' => $avatarHref - ), - '' - ); - - $xs->elementEnd('activity:' . $element); - - return $xs->getString(); } /** @@ -486,36 +422,6 @@ class Ostatus_profile extends Memcached_DataObject } } - function atomFeed($actor) - { - $feed = new Atom10Feed(); - // @fixme should these be set up somewhere else? - $feed->addNamespace('activity', 'http://activitystrea.ms/spec/1.0/'); - $feed->addNamespace('thr', 'http://purl.org/syndication/thread/1.0'); - $feed->addNamespace('georss', 'http://www.georss.org/georss'); - $feed->addNamespace('ostatus', 'http://ostatus.org/schema/1.0'); - - $taguribase = common_config('integration', 'taguri'); - $feed->setId("tag:{$taguribase}:UserTimeline:{$actor->id}"); // ??? - - $feed->setTitle($actor->getBestName() . ' timeline'); // @fixme - $feed->setUpdated(time()); - $feed->setPublished(time()); - - $feed->addLink(common_local_url('ApiTimelineUser', - array('id' => $actor->id, - 'type' => 'atom')), - array('rel' => 'self', - 'type' => 'application/atom+xml')); - - $feed->addLink(common_local_url('userbyid', - array('id' => $actor->id)), - array('rel' => 'alternate', - 'type' => 'text/html')); - - return $feed; - } - /** * Read and post notices for updates from the feed. * Currently assumes that all items in the feed are new, @@ -791,11 +697,18 @@ class Ostatus_profile extends Memcached_DataObject { // Get the canonical feed URI and check it $discover = new FeedDiscovery(); - $feeduri = $discover->discoverFromURL($profile_uri); + if ($hints['feedurl']) { + $feeduri = $hints['feedurl']; + $feeduri = $discover->discoverFromFeedURL($feeduri); + } else { + $feeduri = $discover->discoverFromURL($profile_uri); + $hints['feedurl'] = $feeduri; + } - //$feedsub = FeedSub::ensureFeed($feeduri, $discover->feed); $huburi = $discover->getAtomLink('hub'); + $hints['hub'] = $huburi; $salmonuri = $discover->getAtomLink('salmon'); + $hints['salmon'] = $salmonuri; if (!$huburi) { // We can only deal with folks with a PuSH hub @@ -810,7 +723,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($subject)) { $subjObject = new ActivityObject($subject); - return self::ensureActivityObjectProfile($subjObject, $feeduri, $salmonuri, $hints); + return self::ensureActivityObjectProfile($subjObject, $hints); } // Otherwise, try the feed author @@ -819,7 +732,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($author)) { $authorObject = new ActivityObject($author); - return self::ensureActivityObjectProfile($authorObject, $feeduri, $salmonuri, $hints); + return self::ensureActivityObjectProfile($authorObject, $hints); } // Sheesh. Not a very nice feed! Let's try fingerpoken in the @@ -835,7 +748,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($actor)) { $actorObject = new ActivityObject($actor); - return self::ensureActivityObjectProfile($actorObject, $feeduri, $salmonuri, $hints); + return self::ensureActivityObjectProfile($actorObject, $hints); } @@ -843,7 +756,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($author)) { $authorObject = new ActivityObject($author); - return self::ensureActivityObjectProfile($authorObject, $feeduri, $salmonuri, $hints); + return self::ensureActivityObjectProfile($authorObject, $hints); } } @@ -988,18 +901,18 @@ class Ostatus_profile extends Memcached_DataObject * @return Ostatus_profile */ - public static function ensureActorProfile($activity, $feeduri=null, $salmonuri=null) + public static function ensureActorProfile($activity, $hints=array()) { - return self::ensureActivityObjectProfile($activity->actor, $feeduri, $salmonuri); + return self::ensureActivityObjectProfile($activity->actor, $hints); } - public static function ensureActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array()) + public static function ensureActivityObjectProfile($object, $hints=array()) { $profile = self::getActivityObjectProfile($object); if ($profile) { $profile->updateFromActivityObject($object, $hints); } else { - $profile = self::createActivityObjectProfile($object, $feeduri, $salmonuri, $hints); + $profile = self::createActivityObjectProfile($object, $hints); } return $profile; } @@ -1045,58 +958,55 @@ class Ostatus_profile extends Memcached_DataObject * @fixme validate stuff somewhere */ - protected static function createActorProfile($activity, $feeduri=null, $salmonuri=null) - { - $actor = $activity->actor; - - self::createActivityObjectProfile($actor, $feeduri, $salmonuri); - } - /** * Create local ostatus_profile and profile/user_group entries for * the provided remote user or group. * * @param ActivityObject $object - * @param string $feeduri - * @param string $salmonuri * @param array $hints * - * @fixme fold $feeduri/$salmonuri into $hints * @return Ostatus_profile */ - protected static function createActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array()) + protected static function createActivityObjectProfile($object, $hints=array()) { - $homeuri = $object->id; + $homeuri = $object->id; + $discover = false; if (!$homeuri) { common_log(LOG_DEBUG, __METHOD__ . " empty actor profile URI: " . var_export($activity, true)); throw new ServerException("No profile URI"); } - if (empty($feeduri)) { - if (array_key_exists('feedurl', $hints)) { - $feeduri = $hints['feedurl']; - } + if (array_key_exists('feedurl', $hints)) { + $feeduri = $hints['feedurl']; + } else { + $discover = new FeedDiscovery(); + $feeduri = $discover->discoverFromURL($homeuri); } - if (empty($salmonuri)) { - if (array_key_exists('salmon', $hints)) { - $salmonuri = $hints['salmon']; + if (array_key_exists('salmon', $hints)) { + $salmonuri = $hints['salmon']; + } else { + if (!$discover) { + $discover = new FeedDiscovery(); + $discover->discoverFromFeedURL($hints['feedurl']); } + $salmonuri = $discover->getAtomLink('salmon'); } - if (!$feeduri || !$salmonuri) { - // Get the canonical feed URI and check it - $discover = new FeedDiscovery(); - $feeduri = $discover->discoverFromURL($homeuri); - + if (array_key_exists('hub', $hints)) { + $huburi = $hints['hub']; + } else { + if (!$discover) { + $discover = new FeedDiscovery(); + $discover->discoverFromFeedURL($hints['feedurl']); + } $huburi = $discover->getAtomLink('hub'); - $salmonuri = $discover->getAtomLink('salmon'); + } - if (!$huburi) { - // We can only deal with folks with a PuSH hub - throw new FeedSubNoHubException(); - } + if (!$huburi) { + // We can only deal with folks with a PuSH hub + throw new FeedSubNoHubException(); } $oprofile = new Ostatus_profile(); @@ -1375,9 +1285,9 @@ class Ostatus_profile extends Memcached_DataObject // Now, try some discovery - $wf = new Webfinger(); + $disco = new Discovery(); - $result = $wf->lookup($addr); + $result = $disco->lookup($addr); if (!$result) { self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), null); @@ -1386,16 +1296,16 @@ class Ostatus_profile extends Memcached_DataObject foreach ($result->links as $link) { switch ($link['rel']) { - case Webfinger::PROFILEPAGE: + case Discovery::PROFILEPAGE: $profileUrl = $link['href']; break; case 'salmon': $salmonEndpoint = $link['href']; break; - case Webfinger::UPDATESFROM: + case Discovery::UPDATESFROM: $feedUrl = $link['href']; break; - case Webfinger::HCARD: + case Discovery::HCARD: $hcardUrl = $link['href']; break; default: diff --git a/plugins/OStatus/lib/discovery.php b/plugins/OStatus/lib/discovery.php new file mode 100644 index 000000000..388df0a28 --- /dev/null +++ b/plugins/OStatus/lib/discovery.php @@ -0,0 +1,310 @@ +<?php +/** + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, StatusNet, Inc. + * + * A sample module to show best practices for StatusNet plugins + * + * PHP version 5 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @package StatusNet + * @author James Walker <james@status.net> + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +/** + * This class implements LRDD-based service discovery based on the "Hammer Draft" + * (including webfinger) + * + * @see http://groups.google.com/group/webfinger/browse_thread/thread/9f3d93a479e91bbf + */ +class Discovery +{ + + const LRDD_REL = 'lrdd'; + const PROFILEPAGE = 'http://webfinger.net/rel/profile-page'; + const UPDATESFROM = 'http://schemas.google.com/g/2010#updates-from'; + const HCARD = 'http://microformats.org/profile/hcard'; + + public $methods = array(); + + public function __construct() + { + $this->registerMethod('Discovery_LRDD_Host_Meta'); + $this->registerMethod('Discovery_LRDD_Link_Header'); + $this->registerMethod('Discovery_LRDD_Link_HTML'); + } + + + public function registerMethod($class) + { + $this->methods[] = $class; + } + + /** + * Given a "user id" make sure it's normalized to either a webfinger + * acct: uri or a profile HTTP URL. + */ + public static function normalize($user_id) + { + if (substr($user_id, 0, 5) == 'http:' || + substr($user_id, 0, 6) == 'https:' || + substr($user_id, 0, 5) == 'acct:') { + return $user_id; + } + + if (strpos($user_id, '@') !== FALSE) { + return 'acct:' . $user_id; + } + + return 'http://' . $user_id; + } + + public static function isWebfinger($user_id) + { + $uri = Discovery::normalize($user_id); + + return (substr($uri, 0, 5) == 'acct:'); + } + + /** + * This implements the actual lookup procedure + */ + public function lookup($id) + { + // Normalize the incoming $id to make sure we have a uri + $uri = $this->normalize($id); + + foreach ($this->methods as $class) { + $links = call_user_func(array($class, 'discover'), $uri); + if ($link = Discovery::getService($links, Discovery::LRDD_REL)) { + // Load the LRDD XRD + if ($link['template']) { + $xrd_uri = Discovery::applyTemplate($link['template'], $uri); + } else { + $xrd_uri = $link['href']; + } + + $xrd = $this->fetchXrd($xrd_uri); + if ($xrd) { + return $xrd; + } + } + } + + throw new Exception('Unable to find services for '. $id); + } + + public static function getService($links, $service) { + if (!is_array($links)) { + return false; + } + + foreach ($links as $link) { + if ($link['rel'] == $service) { + return $link; + } + } + } + + + public static function applyTemplate($template, $id) + { + $template = str_replace('{uri}', urlencode($id), $template); + + return $template; + } + + + public static function fetchXrd($url) + { + try { + $client = new HTTPClient(); + $response = $client->get($url); + } catch (HTTP_Request2_Exception $e) { + return false; + } + + if ($response->getStatus() != 200) { + return false; + } + + return XRD::parse($response->getBody()); + } +} + +interface Discovery_LRDD +{ + public function discover($uri); +} + +class Discovery_LRDD_Host_Meta implements Discovery_LRDD +{ + public function discover($uri) + { + if (!Discovery::isWebfinger($uri)) { + return false; + } + + // We have a webfinger acct: - start with host-meta + list($name, $domain) = explode('@', $uri); + $url = 'http://'. $domain .'/.well-known/host-meta'; + + $xrd = Discovery::fetchXrd($url); + + if ($xrd) { + if ($xrd->host != $domain) { + return false; + } + + return $xrd->links; + } + } +} + +class Discovery_LRDD_Link_Header implements Discovery_LRDD +{ + public function discover($uri) + { + try { + $client = new HTTPClient(); + $response = $client->get($uri); + } catch (HTTP_Request2_Exception $e) { + return false; + } + + if ($response->getStatus() != 200) { + return false; + } + + $link_header = $response->getHeader('Link'); + if (!$link_header) { + // return false; + } + + return Discovery_LRDD_Link_Header::parseHeader($link_header); + } + + protected static function parseHeader($header) + { + preg_match('/^<[^>]+>/', $header, $uri_reference); + //if (empty($uri_reference)) return; + + $links = array(); + + $link_uri = trim($uri_reference[0], '<>'); + $link_rel = array(); + $link_type = null; + + // remove uri-reference from header + $header = substr($header, strlen($uri_reference[0])); + + // parse link-params + $params = explode(';', $header); + + foreach ($params as $param) { + if (empty($param)) continue; + list($param_name, $param_value) = explode('=', $param, 2); + $param_name = trim($param_name); + $param_value = preg_replace('(^"|"$)', '', trim($param_value)); + + // for now we only care about 'rel' and 'type' link params + // TODO do something with the other links-params + switch ($param_name) { + case 'rel': + $link_rel = trim($param_value); + break; + + case 'type': + $link_type = trim($param_value); + } + } + + $links[] = array( + 'href' => $link_uri, + 'rel' => $link_rel, + 'type' => $link_type); + + return $links; + } +} + +class Discovery_LRDD_Link_HTML implements Discovery_LRDD +{ + public function discover($uri) + { + try { + $client = new HTTPClient(); + $response = $client->get($uri); + } catch (HTTP_Request2_Exception $e) { + return false; + } + + if ($response->getStatus() != 200) { + return false; + } + + return Discovery_LRDD_Link_HTML::parse($response->getBody()); + } + + + public function parse($html) + { + $links = array(); + + preg_match('/<head(\s[^>]*)?>(.*?)<\/head>/is', $html, $head_matches); + $head_html = $head_matches[2]; + + preg_match_all('/<link\s[^>]*>/i', $head_html, $link_matches); + + foreach ($link_matches[0] as $link_html) { + $link_url = null; + $link_rel = null; + $link_type = null; + + preg_match('/\srel=(("|\')([^\\2]*?)\\2|[^"\'\s]+)/i', $link_html, $rel_matches); + if ( isset($rel_matches[3]) ) { + $link_rel = $rel_matches[3]; + } else if ( isset($rel_matches[1]) ) { + $link_rel = $rel_matches[1]; + } + + preg_match('/\shref=(("|\')([^\\2]*?)\\2|[^"\'\s]+)/i', $link_html, $href_matches); + if ( isset($href_matches[3]) ) { + $link_uri = $href_matches[3]; + } else if ( isset($href_matches[1]) ) { + $link_uri = $href_matches[1]; + } + + preg_match('/\stype=(("|\')([^\\2]*?)\\2|[^"\'\s]+)/i', $link_html, $type_matches); + if ( isset($type_matches[3]) ) { + $link_type = $type_matches[3]; + } else if ( isset($type_matches[1]) ) { + $link_type = $type_matches[1]; + } + + $links[] = array( + 'href' => $link_url, + 'rel' => $link_rel, + 'type' => $link_type, + ); + } + + return $links; + } +} diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index 81f4609c5..457c0fba2 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -50,7 +50,20 @@ class MagicEnvelope public function getKeyPair($signer_uri) { - return 'RSA.79_L2gq-TD72Nsb5yGS0r9stLLpJZF5AHXyxzWmQmlqKl276LEJEs8CppcerLcR90MbYQUwt-SX9slx40Yq3vA==.AQAB.AR-jo5KMfSISmDAT2iMs2_vNFgWRjl5rbJVvA0SpGIEWyPdCGxlPtCbTexp8-0ZEIe8a4SyjatBECH5hxgMTpw=='; + $disco = new Discovery(); + + try { + $xrd = $disco->lookup($signer_uri); + } catch (Exception $e) { + return false; + } + if ($xrd->links) { + if ($link = Discovery::getService($xrd->links, Magicsig::PUBLICKEYREL)) { + list($type, $keypair) = explode(';', $link['href']); + return $keypair; + } + } + throw new Exception('Unable to locate signer public key'); } @@ -59,10 +72,14 @@ class MagicEnvelope $signer_uri = $this->normalizeUser($signer_uri); if (!$this->checkAuthor($text, $signer_uri)) { - return false; + throw new Exception("Unable to determine entry author."); } - $signature_alg = Magicsig::fromString($this->getKeyPair($signer_uri)); + $keypair = $this->getKeyPair($signer_uri); + if (!$keypair) { + throw new Exception("Unable to retrive keypair for ". $signer_uri); + } + $signature_alg = Magicsig::fromString($keypair); $armored_text = base64_encode($text); return array( diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php index b5f178cc6..9d4359f74 100644 --- a/plugins/OStatus/lib/salmon.php +++ b/plugins/OStatus/lib/salmon.php @@ -72,8 +72,12 @@ class Salmon // TODO: Should probably be getting the signer uri as an argument? $signer_uri = $magic_env->getAuthor($text); - $env = $magic_env->signMessage($text, 'application/atom+xml', $signer_uri); - + try { + $env = $magic_env->signMessage($text, 'application/atom+xml', $signer_uri); + } catch (Exception $e) { + common_log(LOG_ERR, "Salmon signing failed: ". $e->getMessage()); + return $text; + } return $magic_env->unfold($env); } diff --git a/plugins/OStatus/lib/webfinger.php b/plugins/OStatus/lib/webfinger.php deleted file mode 100644 index 4b777c9a0..000000000 --- a/plugins/OStatus/lib/webfinger.php +++ /dev/null @@ -1,164 +0,0 @@ -<?php -/** - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2010, StatusNet, Inc. - * - * A sample module to show best practices for StatusNet plugins - * - * PHP version 5 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * @package StatusNet - * @author James Walker <james@status.net> - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - -define('WEBFINGER_SERVICE_REL_VALUE', 'lrdd'); - -/** - * Implement the webfinger protocol. - */ - -class Webfinger -{ - const PROFILEPAGE = 'http://webfinger.net/rel/profile-page'; - const UPDATESFROM = 'http://schemas.google.com/g/2010#updates-from'; - const HCARD = 'http://microformats.org/profile/hcard'; - - /** - * Perform a webfinger lookup given an account. - */ - - public function lookup($id) - { - $id = $this->normalize($id); - list($name, $domain) = explode('@', $id); - - $links = $this->getServiceLinks($domain); - if (!$links) { - return false; - } - - $services = array(); - foreach ($links as $link) { - if ($link['template']) { - return $this->getServiceDescription($link['template'], $id); - } - if ($link['href']) { - return $this->getServiceDescription($link['href'], $id); - } - } - } - - /** - * Normalize an account ID - */ - function normalize($id) - { - if (substr($id, 0, 7) == 'acct://') { - return substr($id, 7); - } else if (substr($id, 0, 5) == 'acct:') { - return substr($id, 5); - } - - return $id; - } - - function getServiceLinks($domain) - { - $url = 'http://'. $domain .'/.well-known/host-meta'; - - $content = $this->fetchURL($url); - - if (empty($content)) { - common_log(LOG_DEBUG, 'Error fetching host-meta'); - return false; - } - - $result = XRD::parse($content); - - // Ensure that the host == domain (spec may include signing later) - if ($result->host != $domain) { - return false; - } - - $links = array(); - foreach ($result->links as $link) { - if ($link['rel'] == WEBFINGER_SERVICE_REL_VALUE) { - $links[] = $link; - } - - } - return $links; - } - - function getServiceDescription($template, $id) - { - $url = $this->applyTemplate($template, 'acct:' . $id); - - $content = $this->fetchURL($url); - - if (!$content) { - return false; - } - - return XRD::parse($content); - } - - function fetchURL($url) - { - try { - $c = Cache::instance(); - $content = $c->get('webfinger:url:'.$url); - if ($content !== false) { - return $content; - } - $client = new HTTPClient(); - $response = $client->get($url); - } catch (HTTP_Request2_Exception $e) { - return false; - } - - if ($response->getStatus() != 200) { - return false; - } - - $body = $response->getBody(); - - $c->set('webfinger:url:'.$url, $body); - - return $body; - } - - function applyTemplate($template, $id) - { - $template = str_replace('{uri}', urlencode($id), $template); - - return $template; - } - - function getHostMeta($domain, $template) { - $xrd = new XRD(); - $xrd->host = $domain; - $xrd->links[] = array('rel' => 'lrdd', - 'template' => $template, - 'title' => array('Resource Descriptor')); - - return $xrd->toXML(); - } -} - |