From 3d0ba3efc84a78a24d4a3a60472e47aef92546dd Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 25 Feb 2010 17:08:50 -0500 Subject: adding a new, more generic "discovery" class that does LRDD disco (rather than webfinger specific) --- plugins/OStatus/lib/discovery.php | 303 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 plugins/OStatus/lib/discovery.php (limited to 'plugins/OStatus/lib/discovery.php') diff --git a/plugins/OStatus/lib/discovery.php b/plugins/OStatus/lib/discovery.php new file mode 100644 index 000000000..1159f2151 --- /dev/null +++ b/plugins/OStatus/lib/discovery.php @@ -0,0 +1,303 @@ +. + * + * @package StatusNet + * @author James Walker + * @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'; + + 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) { + 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 discovery($uri); +} + +class Discovery_LRDD_Host_Meta implements Discovery_LRDD +{ + function discover($uri) + { + if (Discovery::isWebfinger($uri)) { + // We have a webfinger acct: - start with host-meta + list($name, $domain) = explode('@', $id); + } else { + $domain = @parse_url($uri, PHP_URL_HOST); + } + + $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($url); + } 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($header); + } + + protected static function parseHeader($header) + { + preg_match('/^<[^>]+>/', $header, $uri_reference); + if (empty($uri_reference)) return; + + $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); + } + } + + return array( + 'href' => $link_uri, + 'rel' => $link_rel, + 'type' => $link_type); + } +} + +class Discovery_LRDD_Link_HTML implements Discovery_LRDD +{ + public function discover($uri) + { + try { + $client = new HTTPClient(); + $response = $client->get($url); + } 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>/is', $html, $head_matches); + $head_html = $head_matches[2]; + + preg_match_all('/]*>/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; + } +} -- cgit v1.2.3-54-g00ecf From 08413428a7f4613649be3d80fd393a12e33ffbc8 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 25 Feb 2010 17:52:56 -0500 Subject: typo --- plugins/OStatus/lib/discovery.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus/lib/discovery.php') diff --git a/plugins/OStatus/lib/discovery.php b/plugins/OStatus/lib/discovery.php index 1159f2151..8aba31328 100644 --- a/plugins/OStatus/lib/discovery.php +++ b/plugins/OStatus/lib/discovery.php @@ -146,12 +146,12 @@ class Discovery interface Discovery_LRDD { - public function discovery($uri); + public function discover($uri); } class Discovery_LRDD_Host_Meta implements Discovery_LRDD { - function discover($uri) + public function discover($uri) { if (Discovery::isWebfinger($uri)) { // We have a webfinger acct: - start with host-meta -- cgit v1.2.3-54-g00ecf From 84d0c865c4c2dd597e249c76fa1429175f5461a1 Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 26 Feb 2010 03:25:51 -0500 Subject: salmon actually fetching remote keypairs --- plugins/OStatus/OStatusPlugin.php | 14 ++++++++++++++ plugins/OStatus/actions/ostatusinit.php | 2 +- plugins/OStatus/lib/discovery.php | 34 +++++++++++++++++---------------- plugins/OStatus/lib/magicenvelope.php | 5 ++--- 4 files changed, 35 insertions(+), 20 deletions(-) (limited to 'plugins/OStatus/lib/discovery.php') diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 91d055498..46f986682 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -102,6 +102,20 @@ class OStatusPlugin extends Plugin return true; } + /** + * 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. diff --git a/plugins/OStatus/actions/ostatusinit.php b/plugins/OStatus/actions/ostatusinit.php index 5c8575595..8ba8dcdcc 100644 --- a/plugins/OStatus/actions/ostatusinit.php +++ b/plugins/OStatus/actions/ostatusinit.php @@ -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/lib/discovery.php b/plugins/OStatus/lib/discovery.php index 8aba31328..c268ad05c 100644 --- a/plugins/OStatus/lib/discovery.php +++ b/plugins/OStatus/lib/discovery.php @@ -91,7 +91,6 @@ class Discovery 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']) { @@ -141,7 +140,7 @@ class Discovery } return XRD::parse($response->getBody()); - } + } } interface Discovery_LRDD @@ -153,13 +152,12 @@ class Discovery_LRDD_Host_Meta implements Discovery_LRDD { public function discover($uri) { - if (Discovery::isWebfinger($uri)) { - // We have a webfinger acct: - start with host-meta - list($name, $domain) = explode('@', $id); - } else { - $domain = @parse_url($uri, PHP_URL_HOST); + 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); @@ -180,27 +178,29 @@ class Discovery_LRDD_Link_Header implements Discovery_LRDD { try { $client = new HTTPClient(); - $response = $client->get($url); + $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 false; } - return Discovery_LRDD_Link_Header::parseHeader($header); + return Discovery_LRDD_Link_Header::parseHeader($link_header); } protected static function parseHeader($header) { preg_match('/^<[^>]+>/', $header, $uri_reference); - if (empty($uri_reference)) return; + //if (empty($uri_reference)) return; + + $links = array(); $link_uri = trim($uri_reference[0], '<>'); $link_rel = array(); @@ -210,7 +210,7 @@ class Discovery_LRDD_Link_Header implements Discovery_LRDD $header = substr($header, strlen($uri_reference[0])); // parse link-params - $params = explode($header, ';'); + $params = explode(';', $header); foreach ($params as $param) { if (empty($param)) continue; @@ -229,11 +229,13 @@ class Discovery_LRDD_Link_Header implements Discovery_LRDD $link_type = trim($param_value); } } - - return array( + + $links[] = array( 'href' => $link_uri, 'rel' => $link_rel, 'type' => $link_type); + + return $links; } } @@ -243,7 +245,7 @@ class Discovery_LRDD_Link_HTML implements Discovery_LRDD { try { $client = new HTTPClient(); - $response = $client->get($url); + $response = $client->get($uri); } catch (HTTP_Request2_Exception $e) { return false; } diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index 4f8f88155..c642af548 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -52,14 +52,13 @@ class MagicEnvelope { $disco = new Discovery(); - $links = $disco->lookup($signer_uri); - if ($link = Discovery::getService($links, 'magic-public-key')) { + $xrd = $disco->lookup($signer_uri); + 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'); - //return 'RSA.79_L2gq-TD72Nsb5yGS0r9stLLpJZF5AHXyxzWmQmlqKl276LEJEs8CppcerLcR90MbYQUwt-SX9slx40Yq3vA==.AQAB.AR-jo5KMfSISmDAT2iMs2_vNFgWRjl5rbJVvA0SpGIEWyPdCGxlPtCbTexp8-0ZEIe8a4SyjatBECH5hxgMTpw=='; } -- cgit v1.2.3-54-g00ecf From d1256b547f5f4f02ddc51a5fe2146dbde52aac53 Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 26 Feb 2010 03:43:35 -0500 Subject: bad merge.. cleaning up missing webfinger bits --- plugins/OStatus/actions/xrd.php | 2 +- plugins/OStatus/lib/discovery.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus/lib/discovery.php') diff --git a/plugins/OStatus/actions/xrd.php b/plugins/OStatus/actions/xrd.php index 2a754dcfe..e6b694d61 100644 --- a/plugins/OStatus/actions/xrd.php +++ b/plugins/OStatus/actions/xrd.php @@ -66,7 +66,7 @@ class XrdAction extends Action '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))); diff --git a/plugins/OStatus/lib/discovery.php b/plugins/OStatus/lib/discovery.php index c268ad05c..1ab6e51bd 100644 --- a/plugins/OStatus/lib/discovery.php +++ b/plugins/OStatus/lib/discovery.php @@ -39,7 +39,8 @@ 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() -- cgit v1.2.3-54-g00ecf From ab8bb4d79e5f267323440b8cee01458b393ce2d1 Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 26 Feb 2010 04:07:58 -0500 Subject: more cleanup --- plugins/OStatus/classes/Ostatus_profile.php | 2 +- plugins/OStatus/lib/discovery.php | 4 ++++ plugins/OStatus/lib/magicenvelope.php | 21 +++++++++++++++------ 3 files changed, 20 insertions(+), 7 deletions(-) (limited to 'plugins/OStatus/lib/discovery.php') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 4a9aafce1..091056c54 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -1305,7 +1305,7 @@ class Ostatus_profile extends Memcached_DataObject 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 index 1ab6e51bd..388df0a28 100644 --- a/plugins/OStatus/lib/discovery.php +++ b/plugins/OStatus/lib/discovery.php @@ -111,6 +111,10 @@ class Discovery } public static function getService($links, $service) { + if (!is_array($links)) { + return false; + } + foreach ($links as $link) { if ($link['rel'] == $service) { return $link; diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index c642af548..457c0fba2 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -52,12 +52,17 @@ class MagicEnvelope { $disco = new Discovery(); - $xrd = $disco->lookup($signer_uri); - if ($link = Discovery::getService($xrd->links, Magicsig::PUBLICKEYREL)) { - list($type, $keypair) = explode(';', $link['href']); - return $keypair; + 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'); } @@ -70,7 +75,11 @@ class MagicEnvelope 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( -- cgit v1.2.3-54-g00ecf From 45e8819c1b9cc618e9b2d6678b0ff14c653a09d3 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 1 Mar 2010 16:35:36 -0800 Subject: Fix a bunch of notice & warning-level messages that were breaking my inter-instance communications --- plugins/OStatus/classes/Magicsig.php | 4 +++- plugins/OStatus/classes/Ostatus_profile.php | 2 +- plugins/OStatus/lib/discovery.php | 2 +- plugins/OStatus/lib/xrd.php | 11 ++++++++--- 4 files changed, 13 insertions(+), 6 deletions(-) (limited to 'plugins/OStatus/lib/discovery.php') diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index 96900d876..5a46aeeb6 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -146,8 +146,10 @@ class Magicsig extends Memcached_DataObject $mod = base64_url_decode($matches[1]); $exp = base64_url_decode($matches[2]); - if ($matches[4]) { + if (!empty($matches[4])) { $private_exp = base64_url_decode($matches[4]); + } else { + $private_exp = false; } $params['public_key'] = new Crypt_RSA_KEY($mod, $exp, 'public'); diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 7b1aec76b..93e8934c9 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -1145,7 +1145,7 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($poco)) { $url = $poco->getPrimaryURL(); - if ($url->type == 'homepage') { + if ($url && $url->type == 'homepage') { $homepage = $url->value; } } diff --git a/plugins/OStatus/lib/discovery.php b/plugins/OStatus/lib/discovery.php index 388df0a28..f8449b309 100644 --- a/plugins/OStatus/lib/discovery.php +++ b/plugins/OStatus/lib/discovery.php @@ -94,7 +94,7 @@ class Discovery $links = call_user_func(array($class, 'discover'), $uri); if ($link = Discovery::getService($links, Discovery::LRDD_REL)) { // Load the LRDD XRD - if ($link['template']) { + if (!empty($link['template'])) { $xrd_uri = Discovery::applyTemplate($link['template'], $uri); } else { $xrd_uri = $link['href']; diff --git a/plugins/OStatus/lib/xrd.php b/plugins/OStatus/lib/xrd.php index 16d27f8eb..1de065db9 100644 --- a/plugins/OStatus/lib/xrd.php +++ b/plugins/OStatus/lib/xrd.php @@ -53,17 +53,22 @@ class XRD $xrd = new XRD(); $dom = new DOMDocument(); - $dom->loadXML($xml); + if (!$dom->loadXML($xml)) { + throw new Exception("Invalid XML"); + } $xrd_element = $dom->getElementsByTagName('XRD')->item(0); // Check for host-meta host - $host = $xrd_element->getElementsByTagName('Host')->item(0)->nodeValue; + $host = $xrd_element->getElementsByTagName('Host')->item(0); if ($host) { - $xrd->host = $host; + $xrd->host = $host->nodeValue; } // Loop through other elements foreach ($xrd_element->childNodes as $node) { + if (!($node instanceof DOMElement)) { + continue; + } switch ($node->tagName) { case 'Expires': $xrd->expires = $node->nodeValue; -- cgit v1.2.3-54-g00ecf