summaryrefslogtreecommitdiff
path: root/plugins/OStatus/lib
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/OStatus/lib')
-rw-r--r--plugins/OStatus/lib/discovery.php91
-rw-r--r--plugins/OStatus/lib/discoveryhints.php252
-rw-r--r--plugins/OStatus/lib/feeddiscovery.php33
-rw-r--r--plugins/OStatus/lib/linkheader.php63
-rw-r--r--plugins/OStatus/lib/magicenvelope.php21
-rw-r--r--plugins/OStatus/lib/safecrypt_rsa.php18
-rw-r--r--plugins/OStatus/lib/safemath_biginteger.php20
-rw-r--r--plugins/OStatus/lib/xrdaction.php8
8 files changed, 423 insertions, 83 deletions
diff --git a/plugins/OStatus/lib/discovery.php b/plugins/OStatus/lib/discovery.php
index f8449b309..7187c1f3e 100644
--- a/plugins/OStatus/lib/discovery.php
+++ b/plugins/OStatus/lib/discovery.php
@@ -40,7 +40,7 @@ class Discovery
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()
@@ -50,12 +50,11 @@ class Discovery
$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.
@@ -78,7 +77,7 @@ class Discovery
public static function isWebfinger($user_id)
{
$uri = Discovery::normalize($user_id);
-
+
return (substr($uri, 0, 5) == 'acct:');
}
@@ -99,7 +98,7 @@ class Discovery
} else {
$xrd_uri = $link['href'];
}
-
+
$xrd = $this->fetchXrd($xrd_uri);
if ($xrd) {
return $xrd;
@@ -114,14 +113,13 @@ class Discovery
if (!is_array($links)) {
return false;
}
-
+
foreach ($links as $link) {
if ($link['rel'] == $service) {
return $link;
}
}
}
-
public static function applyTemplate($template, $id)
{
@@ -130,7 +128,6 @@ class Discovery
return $template;
}
-
public static function fetchXrd($url)
{
try {
@@ -157,12 +154,13 @@ class Discovery_LRDD_Host_Meta implements Discovery_LRDD
{
public function discover($uri)
{
- if (!Discovery::isWebfinger($uri)) {
- return false;
+ if (Discovery::isWebfinger($uri)) {
+ // We have a webfinger acct: - start with host-meta
+ list($name, $domain) = explode('@', $uri);
+ } else {
+ $domain = parse_url($uri, PHP_URL_HOST);
}
-
- // 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);
@@ -171,7 +169,7 @@ class Discovery_LRDD_Host_Meta implements Discovery_LRDD
if ($xrd->host != $domain) {
return false;
}
-
+
return $xrd->links;
}
}
@@ -187,7 +185,7 @@ class Discovery_LRDD_Link_Header implements Discovery_LRDD
} catch (HTTP_Request2_Exception $e) {
return false;
}
-
+
if ($response->getStatus() != 200) {
return false;
}
@@ -196,51 +194,17 @@ class Discovery_LRDD_Link_Header implements Discovery_LRDD
if (!$link_header) {
// return false;
}
-
- return Discovery_LRDD_Link_Header::parseHeader($link_header);
+
+ return array(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);
+ $lh = new LinkHeader($header);
- return $links;
+ return array('href' => $lh->href,
+ 'rel' => $lh->rel,
+ 'type' => $lh->type);
}
}
@@ -262,49 +226,48 @@ class Discovery_LRDD_Link_HTML implements Discovery_LRDD
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/discoveryhints.php b/plugins/OStatus/lib/discoveryhints.php
new file mode 100644
index 000000000..80cfbbf15
--- /dev/null
+++ b/plugins/OStatus/lib/discoveryhints.php
@@ -0,0 +1,252 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * Some utilities for generating hint data
+ *
+ * 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/>.
+ */
+
+class DiscoveryHints {
+
+ static function fromXRD($xrd)
+ {
+ $hints = array();
+
+ foreach ($xrd->links as $link) {
+ switch ($link['rel']) {
+ case Discovery::PROFILEPAGE:
+ $hints['profileurl'] = $link['href'];
+ break;
+ case Salmon::NS_REPLIES:
+ $hints['salmon'] = $link['href'];
+ break;
+ case Discovery::UPDATESFROM:
+ $hints['feedurl'] = $link['href'];
+ break;
+ case Discovery::HCARD:
+ $hints['hcardurl'] = $link['href'];
+ break;
+ default:
+ break;
+ }
+ }
+
+ return $hints;
+ }
+
+ static function fromHcardUrl($url)
+ {
+ $client = new HTTPClient();
+ $client->setHeader('Accept', 'text/html,application/xhtml+xml');
+ $response = $client->get($url);
+
+ if (!$response->isOk()) {
+ return null;
+ }
+
+ return self::hcardHints($response->getBody(),
+ $response->getUrl());
+ }
+
+ static function hcardHints($body, $url)
+ {
+ $hcard = self::_hcard($body, $url);
+
+ if (empty($hcard)) {
+ return array();
+ }
+
+ $hints = array();
+
+ // XXX: don't copy stuff into an array and then copy it again
+
+ if (array_key_exists('nickname', $hcard)) {
+ $hints['nickname'] = $hcard['nickname'];
+ }
+
+ if (array_key_exists('fn', $hcard)) {
+ $hints['fullname'] = $hcard['fn'];
+ } else if (array_key_exists('n', $hcard)) {
+ $hints['fullname'] = implode(' ', $hcard['n']);
+ }
+
+ if (array_key_exists('photo', $hcard)) {
+ $hints['avatar'] = $hcard['photo'][0];
+ }
+
+ if (array_key_exists('note', $hcard)) {
+ $hints['bio'] = $hcard['note'];
+ }
+
+ if (array_key_exists('adr', $hcard)) {
+ if (is_string($hcard['adr'])) {
+ $hints['location'] = $hcard['adr'];
+ } else if (is_array($hcard['adr'])) {
+ $hints['location'] = implode(' ', $hcard['adr']);
+ }
+ }
+
+ if (array_key_exists('url', $hcard)) {
+ if (is_string($hcard['url'])) {
+ $hints['homepage'] = $hcard['url'];
+ } else if (is_array($hcard['url']) && !empty($hcard['url'])) {
+ // HACK get the last one; that's how our hcards look
+ $hints['homepage'] = $hcard['url'][count($hcard['url'])-1];
+ }
+ }
+
+ return $hints;
+ }
+
+ static function _hcard($body, $url)
+ {
+ // DOMDocument::loadHTML may throw warnings on unrecognized elements.
+
+ $old = error_reporting(error_reporting() & ~E_WARNING);
+
+ $doc = new DOMDocument();
+ $doc->loadHTML($body);
+
+ error_reporting($old);
+
+ $xp = new DOMXPath($doc);
+
+ $hcardNodes = self::_getChildrenByClass($doc->documentElement, 'vcard', $xp);
+
+ $hcards = array();
+
+ for ($i = 0; $i < $hcardNodes->length; $i++) {
+
+ $hcardNode = $hcardNodes->item($i);
+
+ $hcard = self::_hcardFromNode($hcardNode, $xp, $url);
+
+ $hcards[] = $hcard;
+ }
+
+ $repr = null;
+
+ foreach ($hcards as $hcard) {
+ if (in_array($url, $hcard['url'])) {
+ $repr = $hcard;
+ break;
+ }
+ }
+
+ if (!is_null($repr)) {
+ return $repr;
+ } else if (count($hcards) > 0) {
+ return $hcards[0];
+ } else {
+ return null;
+ }
+ }
+
+ function _getChildrenByClass($el, $cls, $xp)
+ {
+ // borrowed from hkit. Thanks dudes!
+
+ $qry = ".//*[contains(concat(' ',normalize-space(@class),' '),' $cls ')]";
+
+ $nodes = $xp->query($qry, $el);
+
+ return $nodes;
+ }
+
+ function _hcardFromNode($hcardNode, $xp, $base)
+ {
+ $hcard = array();
+
+ $hcard['url'] = array();
+
+ $urlNodes = self::_getChildrenByClass($hcardNode, 'url', $xp);
+
+ for ($j = 0; $j < $urlNodes->length; $j++) {
+
+ $urlNode = $urlNodes->item($j);
+
+ if ($urlNode->hasAttribute('href')) {
+ $url = $urlNode->getAttribute('href');
+ } else {
+ $url = $urlNode->textContent;
+ }
+
+ $hcard['url'][] = self::_rel2abs($url, $base);
+ }
+
+ $hcard['photo'] = array();
+
+ $photoNodes = self::_getChildrenByClass($hcardNode, 'photo', $xp);
+
+ for ($j = 0; $j < $photoNodes->length; $j++) {
+ $photoNode = $photoNodes->item($j);
+ if ($photoNode->hasAttribute('src')) {
+ $url = $photoNode->getAttribute('src');
+ } else if ($photoNode->hasAttribute('href')) {
+ $url = $photoNode->getAttribute('href');
+ } else {
+ $url = $photoNode->textContent;
+ }
+ $hcard['photo'][] = self::_rel2abs($url, $base);
+ }
+
+ $singles = array('nickname', 'note', 'fn', 'n', 'adr');
+
+ foreach ($singles as $single) {
+
+ $nodes = self::_getChildrenByClass($hcardNode, $single, $xp);
+
+ if ($nodes->length > 0) {
+ $node = $nodes->item(0);
+ $hcard[$single] = $node->textContent;
+ }
+ }
+
+ return $hcard;
+ }
+
+ // XXX: this is a first pass; we probably need
+ // to handle things like ../ and ./ and so on
+
+ static function _rel2abs($rel, $wrt)
+ {
+ $parts = parse_url($rel);
+
+ if ($parts === false) {
+ return false;
+ }
+
+ // If it's got a scheme, use it
+
+ if (!empty($parts['scheme'])) {
+ return $rel;
+ }
+
+ $w = parse_url($wrt);
+
+ $base = $w['scheme'].'://'.$w['host'];
+
+ if ($rel[0] == '/') {
+ return $base.$rel;
+ }
+
+ $wp = explode('/', $w['path']);
+
+ array_pop($wp);
+
+ return $base.implode('/', $wp).'/'.$rel;
+ }
+}
diff --git a/plugins/OStatus/lib/feeddiscovery.php b/plugins/OStatus/lib/feeddiscovery.php
index 7afb71bdc..4809f9d35 100644
--- a/plugins/OStatus/lib/feeddiscovery.php
+++ b/plugins/OStatus/lib/feeddiscovery.php
@@ -73,6 +73,7 @@ class FeedDiscovery
public $uri;
public $type;
public $feed;
+ public $root;
/** Post-initialize query helper... */
public function getLink($rel, $type=null)
@@ -83,7 +84,7 @@ class FeedDiscovery
public function getAtomLink($rel, $type=null)
{
- return ActivityUtils::getLink($this->feed->documentElement, $rel, $type);
+ return ActivityUtils::getLink($this->root, $rel, $type);
}
/**
@@ -117,7 +118,7 @@ class FeedDiscovery
return $this->discoverFromURL($target, false);
}
}
-
+
return $this->initFromResponse($response);
}
@@ -129,7 +130,7 @@ class FeedDiscovery
function initFromResponse($response)
{
if (!$response->isOk()) {
- throw new FeedSubBadResponseException($response->getCode());
+ throw new FeedSubBadResponseException($response->getStatus());
}
$sourceurl = $response->getUrl();
@@ -154,9 +155,27 @@ class FeedDiscovery
$this->uri = $sourceurl;
$this->type = $type;
$this->feed = $feed;
+
+ $el = $this->feed->documentElement;
+
+ // Looking for the "root" element: RSS channel or Atom feed
+
+ if ($el->tagName == 'rss') {
+ $channels = $el->getElementsByTagName('channel');
+ if ($channels->length > 0) {
+ $this->root = $channels->item(0);
+ } else {
+ throw new FeedSubBadXmlException($sourceurl);
+ }
+ } else if ($el->tagName == 'feed') {
+ $this->root = $el;
+ } else {
+ throw new FeedSubBadXmlException($sourceurl);
+ }
+
return $this->uri;
} else {
- throw new FeedSubBadXmlException($url);
+ throw new FeedSubBadXmlException($sourceurl);
}
}
@@ -202,7 +221,7 @@ class FeedDiscovery
'application/atom+xml' => false,
'application/rss+xml' => false,
);
-
+
$nodes = $dom->getElementsByTagName('link');
for ($i = 0; $i < $nodes->length; $i++) {
$node = $nodes->item($i);
@@ -211,11 +230,11 @@ class FeedDiscovery
$type = $node->attributes->getNamedItem('type');
$href = $node->attributes->getNamedItem('href');
if ($rel && $type && $href) {
- $rel = trim($rel->value);
+ $rel = array_filter(explode(" ", $rel->value));
$type = trim($type->value);
$href = trim($href->value);
- if (trim($rel) == 'alternate' && array_key_exists($type, $feeds) && empty($feeds[$type])) {
+ if (in_array('alternate', $rel) && array_key_exists($type, $feeds) && empty($feeds[$type])) {
// Save the first feed found of each type...
$feeds[$type] = $this->resolveURI($href, $base);
}
diff --git a/plugins/OStatus/lib/linkheader.php b/plugins/OStatus/lib/linkheader.php
new file mode 100644
index 000000000..cd78d31ce
--- /dev/null
+++ b/plugins/OStatus/lib/linkheader.php
@@ -0,0 +1,63 @@
+<?php
+
+class LinkHeader
+{
+ var $href;
+ var $rel;
+ var $type;
+
+ function __construct($str)
+ {
+ preg_match('/^<[^>]+>/', $str, $uri_reference);
+ //if (empty($uri_reference)) return;
+
+ $this->href = trim($uri_reference[0], '<>');
+ $this->rel = array();
+ $this->type = null;
+
+ // remove uri-reference from header
+ $str = substr($str, strlen($uri_reference[0]));
+
+ // parse link-params
+ $params = explode(';', $str);
+
+ 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':
+ $this->rel = trim($param_value);
+ break;
+
+ case 'type':
+ $this->type = trim($param_value);
+ }
+ }
+ }
+
+ static function getLink($response, $rel=null, $type=null)
+ {
+ $headers = $response->getHeader('Link');
+ if ($headers) {
+ // Can get an array or string, so try to simplify the path
+ if (!is_array($headers)) {
+ $headers = array($headers);
+ }
+
+ foreach ($headers as $header) {
+ $lh = new LinkHeader($header);
+
+ if ((is_null($rel) || $lh->rel == $rel) &&
+ (is_null($type) || $lh->type == $type)) {
+ return $lh->href;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php
index fb8c57c71..9266cab5c 100644
--- a/plugins/OStatus/lib/magicenvelope.php
+++ b/plugins/OStatus/lib/magicenvelope.php
@@ -59,7 +59,11 @@ class MagicEnvelope
}
if ($xrd->links) {
if ($link = Discovery::getService($xrd->links, Magicsig::PUBLICKEYREL)) {
- list($type, $keypair) = explode(';', $link['href']);
+ list($type, $keypair) = explode(',', $link['href']);
+ if (empty($keypair)) {
+ // Backwards compatibility check for separator bug in 0.9.0
+ list($type, $keypair) = explode(';', $link['href']);
+ }
return $keypair;
}
}
@@ -70,7 +74,7 @@ class MagicEnvelope
public function signMessage($text, $mimetype, $keypair)
{
$signature_alg = Magicsig::fromString($keypair);
- $armored_text = base64_encode($text);
+ $armored_text = base64_url_encode($text);
return array(
'data' => $armored_text,
@@ -108,7 +112,7 @@ class MagicEnvelope
public function unfold($env)
{
$dom = new DOMDocument();
- $dom->loadXML(base64_decode($env['data']));
+ $dom->loadXML(base64_url_decode($env['data']));
if ($dom->documentElement->tagName != 'entry') {
return false;
@@ -165,7 +169,7 @@ class MagicEnvelope
return false;
}
- $text = base64_decode($env['data']);
+ $text = base64_url_decode($env['data']);
$signer_uri = $this->getAuthor($text);
try {
@@ -193,11 +197,12 @@ class MagicEnvelope
public function fromDom($dom)
{
- if ($dom->documentElement->tagName == 'entry') {
+ $env_element = $dom->getElementsByTagNameNS(MagicEnvelope::NS, 'env')->item(0);
+ if (!$env_element) {
$env_element = $dom->getElementsByTagNameNS(MagicEnvelope::NS, 'provenance')->item(0);
- } else if ($dom->documentElement->tagName == 'me:env') {
- $env_element = $dom->documentElement;
- } else {
+ }
+
+ if (!$env_element) {
return false;
}
diff --git a/plugins/OStatus/lib/safecrypt_rsa.php b/plugins/OStatus/lib/safecrypt_rsa.php
new file mode 100644
index 000000000..f3aa2c928
--- /dev/null
+++ b/plugins/OStatus/lib/safecrypt_rsa.php
@@ -0,0 +1,18 @@
+<?php
+
+require_once 'Crypt/RSA.php';
+
+/**
+ * Crypt_RSA stores a Math_BigInteger with value 0, which triggers a bug
+ * in Math_BigInteger's wakeup function which spews notices to log or output.
+ * This wrapper replaces it with a version that survives serialization.
+ */
+class SafeCrypt_RSA extends Crypt_RSA
+{
+ function __construct()
+ {
+ parent::__construct();
+ $this->zero = new SafeMath_BigInteger();
+ }
+}
+
diff --git a/plugins/OStatus/lib/safemath_biginteger.php b/plugins/OStatus/lib/safemath_biginteger.php
new file mode 100644
index 000000000..c05e24d1e
--- /dev/null
+++ b/plugins/OStatus/lib/safemath_biginteger.php
@@ -0,0 +1,20 @@
+<?php
+
+require_once 'Math/BigInteger.php';
+
+/**
+ * Crypt_RSA stores a Math_BigInteger with value 0, which triggers a bug
+ * in Math_BigInteger's wakeup function which spews notices to log or output.
+ * This wrapper replaces it with a version that survives serialization.
+ */
+class SafeMath_BigInteger extends Math_BigInteger
+{
+ function __wakeup()
+ {
+ if ($this->hex == '') {
+ $this->hex = '0';
+ }
+ parent::__wakeup();
+ }
+}
+
diff --git a/plugins/OStatus/lib/xrdaction.php b/plugins/OStatus/lib/xrdaction.php
index 6881292ad..f1a56e0a8 100644
--- a/plugins/OStatus/lib/xrdaction.php
+++ b/plugins/OStatus/lib/xrdaction.php
@@ -46,10 +46,10 @@ class XrdAction extends Action
if (empty($xrd->subject)) {
$xrd->subject = Discovery::normalize($this->uri);
}
- $xrd->alias[] = common_profile_url($nick);
+ $xrd->alias[] = $this->user->uri;
$xrd->links[] = array('rel' => Discovery::PROFILEPAGE,
'type' => 'text/html',
- 'href' => common_profile_url($nick));
+ 'href' => $this->user->uri);
$xrd->links[] = array('rel' => Discovery::UPDATESFROM,
'href' => common_local_url('ApiTimelineUser',
@@ -65,7 +65,7 @@ class XrdAction extends Action
// XFN
$xrd->links[] = array('rel' => 'http://gmpg.org/xfn/11',
'type' => 'text/html',
- 'href' => common_profile_url($nick));
+ 'href' => $this->user->uri);
// FOAF
$xrd->links[] = array('rel' => 'describedby',
'type' => 'application/rdf+xml',
@@ -91,7 +91,7 @@ class XrdAction extends Action
}
$xrd->links[] = array('rel' => Magicsig::PUBLICKEYREL,
- 'href' => 'data:application/magic-public-key;'. $magickey->toString(false));
+ 'href' => 'data:application/magic-public-key,'. $magickey->toString(false));
// TODO - finalize where the redirect should go on the publisher
$url = common_local_url('ostatussub') . '?profile={uri}';