common_log(LOG_INFO, "Posting to Salmon endpoint $this->salmonuri: $xml");
$salmon = new Salmon(); // ?
- return $salmon->post($this->salmonuri, $xml);
+ return $salmon->post($this->salmonuri, $xml, $actor);
}
return false;
}
* @param mixed $entry XML string, Notice, or Activity
* @return boolean success
*/
- public function notifyActivity($entry)
+ public function notifyActivity($entry, $actor)
{
if ($this->salmonuri) {
$salmon = new Salmon();
- return $salmon->post($this->salmonuri, $this->notifyPrepXml($entry));
+ return $salmon->post($this->salmonuri, $this->notifyPrepXml($entry), $actor);
}
return false;
* @param mixed $entry XML string, Notice, or Activity
* @return boolean success
*/
- public function notifyDeferred($entry)
+ public function notifyDeferred($entry, $actor)
{
if ($this->salmonuri) {
$data = array('salmonuri' => $this->salmonuri,
- 'entry' => $this->notifyPrepXml($entry));
+ 'entry' => $this->notifyPrepXml($entry),
+ 'actor' => $actor->id);
$qm = QueueManager::get();
return $qm->enqueue($data, 'salmon');
* Currently assumes that all items in the feed are new,
* coming from a PuSH hub.
*
- * @param DOMDocument $feed
+ * @param DOMDocument $doc
+ * @param string $source identifier ("push")
*/
- public function processFeed($feed, $source)
+ public function processFeed(DOMDocument $doc, $source)
{
+ $feed = $doc->documentElement;
+
+ if ($feed->localName != 'feed' || $feed->namespaceURI != Activity::ATOM) {
+ common_log(LOG_ERR, __METHOD__ . ": not an Atom feed, ignoring");
+ return;
+ }
+
$entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
if ($entries->length == 0) {
common_log(LOG_ERR, __METHOD__ . ": no entries in feed update, ignoring");
*
* @param DOMElement $entry
* @param DOMElement $feed for context
+ * @param string $source identifier ("push" or "salmon")
*/
public function processEntry($entry, $feed, $source)
{
'rendered' => $rendered,
'replies' => array(),
'groups' => array(),
- 'tags' => array());
+ 'tags' => array(),
+ 'urls' => array());
// Check for optional attributes...
}
}
+ // Atom enclosures -> attachment URLs
+ foreach ($activity->enclosures as $href) {
+ // @fixme save these locally or....?
+ $options['urls'][] = $href;
+ }
+
try {
$saved = Notice::saveNew($oprofile->profile_id,
$content,
protected function purify($html)
{
require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php';
- $config = array('safe' => 1);
+ $config = array('safe' => 1,
+ 'deny_attribute' => 'id,style,on*');
return htmLawed($html, $config);
}
}
// Is the recipient a local group?
- // @fixme we need a uri on user_group
+ // @fixme uri on user_group isn't reliable yet
// $group = User_group::staticGet('uri', $recipient);
- $template = common_local_url('groupbyid', array('id' => '31337'));
- $template = preg_quote($template, '/');
- $template = str_replace('31337', '(\d+)', $template);
- if (preg_match("/$template/", $recipient, $matches)) {
- $id = $matches[1];
+ $id = OStatusPlugin::localGroupFromUrl($recipient);
+ if ($id) {
$group = User_group::staticGet('id', $id);
if ($group) {
// Deliver to all members of this local group if allowed.
* @return Ostatus_profile
* @throws FeedSubException
*/
- public static function ensureProfile($profile_uri, $hints=array())
+
+ public static function ensureProfileURL($profile_url, $hints=array())
{
- // Get the canonical feed URI and check it
+ $oprofile = self::getFromProfileURL($profile_url);
+
+ if (!empty($oprofile)) {
+ return $oprofile;
+ }
+
+ $hints['profileurl'] = $profile_url;
+
+ // Fetch the URL
+ // XXX: HTTP caching
+
+ $client = new HTTPClient();
+ $client->setHeader('Accept', 'text/html,application/xhtml+xml');
+ $response = $client->get($profile_url);
+
+ if (!$response->isOk()) {
+ return null;
+ }
+
+ // Check if we have a non-canonical URL
+
+ $finalUrl = $response->getUrl();
+
+ if ($finalUrl != $profile_url) {
+
+ $hints['profileurl'] = $finalUrl;
+
+ $oprofile = self::getFromProfileURL($finalUrl);
+
+ if (!empty($oprofile)) {
+ return $oprofile;
+ }
+ }
+
+ // Try to get some hCard data
+
+ $body = $response->getBody();
+
+ $hcardHints = DiscoveryHints::hcardHints($body, $finalUrl);
+
+ if (!empty($hcardHints)) {
+ $hints = array_merge($hints, $hcardHints);
+ }
+
+ // Check if they've got an LRDD header
+
+ $lrdd = LinkHeader::getLink($response, 'lrdd', 'application/xrd+xml');
+
+ if (!empty($lrdd)) {
+
+ $xrd = Discovery::fetchXrd($lrdd);
+ $xrdHints = DiscoveryHints::fromXRD($xrd);
+
+ $hints = array_merge($hints, $xrdHints);
+ }
+
+ // If discovery found a feedurl (probably from LRDD), use it.
+
+ if (array_key_exists('feedurl', $hints)) {
+ return self::ensureFeedURL($hints['feedurl'], $hints);
+ }
+
+ // Get the feed URL from HTML
+
$discover = new FeedDiscovery();
- if ($hints['feedurl']) {
- $feeduri = $hints['feedurl'];
- $feeduri = $discover->discoverFromFeedURL($feeduri);
- } else {
- $feeduri = $discover->discoverFromURL($profile_uri);
- $hints['feedurl'] = $feeduri;
+
+ $feedurl = $discover->discoverFromHTML($finalUrl, $body);
+
+ if (!empty($feedurl)) {
+ $hints['feedurl'] = $feedurl;
+
+ return self::ensureFeedURL($feedurl, $hints);
+ }
+ }
+
+ static function getFromProfileURL($profile_url)
+ {
+ $profile = Profile::staticGet('profileurl', $profile_url);
+
+ if (empty($profile)) {
+ return null;
+ }
+
+ // Is it a known Ostatus profile?
+
+ $oprofile = Ostatus_profile::staticGet('profile_id', $profile->id);
+
+ if (!empty($oprofile)) {
+ return $oprofile;
+ }
+
+ // Is it a local user?
+
+ $user = User::staticGet('id', $profile->id);
+
+ if (!empty($user)) {
+ throw new Exception("'$profile_url' is the profile for local user '{$user->nickname}'.");
}
+ // Continue discovery; it's a remote profile
+ // for OMB or some other protocol, may also
+ // support OStatus
+
+ return null;
+ }
+
+ public static function ensureFeedURL($feed_url, $hints=array())
+ {
+ $discover = new FeedDiscovery();
+
+ $feeduri = $discover->discoverFromFeedURL($feed_url);
+ $hints['feedurl'] = $feeduri;
+
$huburi = $discover->getAtomLink('hub');
$hints['hub'] = $huburi;
- $salmonuri = $discover->getAtomLink('salmon');
+ $salmonuri = $discover->getAtomLink(Salmon::NS_REPLIES);
$hints['salmon'] = $salmonuri;
if (!$huburi) {
if (!$homeuri) {
common_log(LOG_DEBUG, __METHOD__ . " empty actor profile URI: " . var_export($activity, true));
- throw new ServerException("No profile URI");
+ throw new Exception("No profile URI");
+ }
+
+ if (OStatusPlugin::localProfileFromUrl($homeuri)) {
+ throw new Exception("Local user can't be referenced as remote.");
+ }
+
+ if (OStatusPlugin::localGroupFromUrl($homeuri)) {
+ throw new Exception("Local group can't be referenced as remote.");
}
if (array_key_exists('feedurl', $hints)) {
$discover = new FeedDiscovery();
$discover->discoverFromFeedURL($hints['feedurl']);
}
- $salmonuri = $discover->getAtomLink('salmon');
+ $salmonuri = $discover->getAtomLink(Salmon::NS_REPLIES);
}
if (array_key_exists('hub', $hints)) {
if (!empty($poco)) {
$url = $poco->getPrimaryURL();
- if ($url->type == 'homepage') {
+ if ($url && $url->type == 'homepage') {
$homepage = $url->value;
}
}
}
}
+ /**
+ * @param string $addr webfinger address
+ * @return Ostatus_profile
+ * @throws Exception on error conditions
+ */
public static function ensureWebfinger($addr)
{
// First, try the cache
if ($uri !== false) {
if (is_null($uri)) {
- return null;
+ // Negative cache entry
+ throw new Exception('Not a valid webfinger address.');
}
$oprofile = Ostatus_profile::staticGet('uri', $uri);
if (!empty($oprofile)) {
}
}
- // First, look it up
+ // Try looking it up
$oprofile = Ostatus_profile::staticGet('uri', 'acct:'.$addr);
$disco = new Discovery();
- $result = $disco->lookup($addr);
-
- if (!$result) {
+ try {
+ $xrd = $disco->lookup($addr);
+ } catch (Exception $e) {
+ // Save negative cache entry so we don't waste time looking it up again.
+ // @fixme distinguish temporary failures?
self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), null);
- return null;
+ throw new Exception('Not a valid webfinger address.');
}
- foreach ($result->links as $link) {
- switch ($link['rel']) {
- case Discovery::PROFILEPAGE:
- $profileUrl = $link['href'];
- break;
- case 'salmon':
- $salmonEndpoint = $link['href'];
- break;
- case Discovery::UPDATESFROM:
- $feedUrl = $link['href'];
- break;
- case Webfinger::HCARD:
- $hcardUrl = $link['href'];
- break;
- default:
- common_log(LOG_NOTICE, "Don't know what to do with rel = '{$link['rel']}'");
- break;
- }
- }
+ $hints = array('webfinger' => $addr);
+
+ $dhints = DiscoveryHints::fromXRD($xrd);
- $hints = array('webfinger' => $addr,
- 'profileurl' => $profileUrl,
- 'feedurl' => $feedUrl,
- 'salmon' => $salmonEndpoint);
+ $hints = array_merge($hints, $dhints);
- if (isset($hcardUrl)) {
- $hcardHints = self::slurpHcard($hcardUrl);
- // Note: Webfinger > hcard
- $hints = array_merge($hcardHints, $hints);
+ // If there's an Hcard, let's grab its info
+
+ if (array_key_exists('hcard', $hints)) {
+ if (!array_key_exists('profileurl', $hints) ||
+ $hints['hcard'] != $hints['profileurl']) {
+ $hcardHints = DiscoveryHints::fromHcardUrl($hints['hcard']);
+ $hints = array_merge($hcardHints, $hints);
+ }
}
// If we got a feed URL, try that
- if (isset($feedUrl)) {
+ if (array_key_exists('feedurl', $hints)) {
try {
common_log(LOG_INFO, "Discovery on acct:$addr with feed URL $feedUrl");
- $oprofile = self::ensureProfile($feedUrl, $hints);
+ $oprofile = self::ensureFeedURL($hints['feedurl'], $hints);
self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->uri);
return $oprofile;
} catch (Exception $e) {
// If we got a profile page, try that!
- if (isset($profileUrl)) {
+ if (array_key_exists('profileurl', $hints)) {
try {
common_log(LOG_INFO, "Discovery on acct:$addr with profile URL $profileUrl");
- $oprofile = self::ensureProfile($profileUrl, $hints);
+ $oprofile = self::ensureProfileURL($hints['profileurl'], $hints);
self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->uri);
return $oprofile;
} catch (Exception $e) {
// XXX: try hcard
// XXX: try FOAF
- if (isset($salmonEndpoint)) {
+ if (array_key_exists('salmon', $hints)) {
+
+ $salmonEndpoint = $hints['salmon'];
// An account URL, a salmon endpoint, and a dream? Not much to go
// on, but let's give it a try
return $oprofile;
}
- return null;
+ throw new Exception("Couldn't find a valid profile for '$addr'");
}
function saveHTMLFile($title, $rendered)
return $file;
}
-
- protected static function slurpHcard($url)
- {
- set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/plugins/OStatus/extlib/hkit/');
- require_once('hkit.class.php');
-
- $h = new hKit;
-
- // Google Buzz hcards need to be tidied. Probably others too.
-
- $h->tidy_mode = 'proxy'; // 'proxy', 'exec', 'php' or 'none'
-
- // Get by URL
- $hcards = $h->getByURL('hcard', $url);
-
- if (empty($hcards)) {
- return array();
- }
-
- // @fixme more intelligent guess on multi-hcard pages
- $hcard = $hcards[0];
-
- $hints = array();
-
- $hints['profileurl'] = $url;
-
- 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'];
- }
-
- 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['adr'])) {
- // HACK get the last one; that's how our hcards look
- $hints['homepage'] = $hcard['url'][count($hcard['url'])-1];
- }
- }
-
- return $hints;
- }
}