- $action: the current action
- &$xrdsoutputter - XRDSOutputter object to write to
-StartHostMetaLinks: Start /.well-known/host-meta links
-- &links: array containing the links elements to be written
-
-EndHostMetaLinks: End /.well-known/host-meta links
-- &links: array containing the links elements to be written
-
StartCheckPassword: Check a username/password
- $nickname: The nickname to check
- $password: The password to check
- $user: user publishing the entry
- $notice: notice that was created
-StartXrdActionAliases: About to set aliases for the XRD object for a user
-- &$xrd: XRD object being shown
-- $user: User being shown
-
-EndXrdActionAliases: Done with aliases for the XRD object for a user
-- &$xrd: XRD object being shown
-- $user: User being shown
-
-StartXrdActionLinks: About to set links for the XRD object for a user
-- &$xrd: XRD object being shown
-- $user: User being shown
-
-EndXrdActionLinks: Done with links for the XRD object for a user
-- &$xrd: XRD object being shown
-- $user: User being shown
-
AdminPanelCheck: When checking whether the current user can access a given admin panel
- $name: Name of the admin panel
- &$isOK: Boolean whether the user is allowed to use the panel
+++ /dev/null
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, Inc.
- *
- * 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/>.
- */
-
-/**
- * @category Action
- * @package StatusNet
- * @maintainer James Walker <james@status.net>
- * @author Craig Andrews <candrews@integralblue.com>
- */
-
-if (!defined('STATUSNET')) {
- exit(1);
-}
-
-// @todo XXX: Add documentation.
-class HostMetaAction extends Action
-{
- /**
- * Is read only?
- *
- * @return boolean true
- */
- function isReadOnly()
- {
- return true;
- }
-
- function handle()
- {
- parent::handle();
-
- $xrd = new XRD();
- $xrd->host = strtolower($_SERVER['SERVER_NAME']);
-
- if(Event::handle('StartHostMetaLinks', array(&$xrd->links))) {
- $url = common_local_url('userxrd');
- $url.= '?uri={uri}';
- $xrd->links[] = array('rel' => Discovery::LRDD_REL,
- 'template' => $url,
- 'title' => array('Resource Descriptor'));
- Event::handle('EndHostMetaLinks', array(&$xrd->links));
- }
-
- // Output Cross-Origin Resource Sharing (CORS) header
- if (common_config('discovery', 'cors')) {
- header('Access-Control-Allow-Origin: *');
- }
-
- header('Content-type: application/xrd+xml');
-
- print $xrd->toXML();
- }
-}
+++ /dev/null
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, Inc.
- *
- * 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/>.
- */
-
-if (!defined('STATUSNET')) {
- exit(1);
-}
-
-/**
- * @package OStatusPlugin
- * @maintainer James Walker <james@status.net>
- */
-class UserxrdAction extends XrdAction
-{
- function prepare($args)
- {
- parent::prepare($args);
- global $config;
-
- $this->uri = $this->trimmed('uri');
- $this->uri = self::normalize($this->uri);
-
- if (self::isWebfinger($this->uri)) {
- $parts = explode('@', substr(urldecode($this->uri), 5));
- if (count($parts) == 2) {
- list($nick, $domain) = $parts;
- // @fixme confirm the domain too
- // @fixme if domain checking is added, ensure that it will not
- // cause problems with sites that have changed domains!
- $nick = common_canonical_nickname($nick);
- $this->user = User::getKV('nickname', $nick);
- }
- } else {
- $this->user = User::getKV('uri', $this->uri);
- if (empty($this->user)) {
- // try and get it by profile url
- $profile = Profile::getKV('profileurl', $this->uri);
- if (!empty($profile)) {
- $this->user = User::getKV('id', $profile->id);
- }
- }
- }
-
- if (!$this->user) {
- // TRANS: Client error displayed when user not found for an action.
- $this->clientError(_('No such user.'), 404);
- return false;
- }
-
- return true;
- }
-}
return $noun->asString('activity:' . $element);
}
+ /**
+ * Returns the profile's canonical url, not necessarily a uri/unique id
+ *
+ * @return string $profileurl
+ */
+ public function getUrl()
+ {
+ if (empty($this->profileurl) ||
+ !filter_var($this->profileurl, FILTER_VALIDATE_URL)) {
+ throw new InvalidUrlException($this->profileurl);
+ }
+ return $this->profileurl;
+ }
+
/**
* Returns the best URI for a profile. Plugins may override.
*
* @return string $uri
*/
- function getUri()
+ public function getUri()
{
$uri = null;
self::cacheSet('user:site_owner', $owner);
}
+ if (!($owner instanceof User)) {
+ throw new ServerException(_('No site owner configured.'));
+ }
+
return $owner;
}
// try the site owner.
if (empty($user)) {
- $user = User::siteOwner();
- }
-
- if (!empty($user)) {
- return $user;
- } else {
- // TRANS: Server exception.
- throw new ServerException(_('No single user defined for single-user mode.'));
+ try {
+ $user = User::siteOwner();
+ return $user;
+ } catch (ServerException $e) {
+ // TRANS: Server exception.
+ throw new ServerException(_('No single user defined for single-user mode.'));
+ }
}
} else {
// TRANS: Server exception.
function isLoginAction($action)
{
- static $loginActions = array('login', 'recoverpassword', 'api', 'doc', 'register', 'publicxrds', 'otp', 'opensearch', 'rsd', 'hostmeta');
+ static $loginActions = array('login', 'recoverpassword', 'api', 'doc', 'register', 'publicxrds', 'otp', 'opensearch', 'rsd');
$login = null;
$svcDocUrl = null;
$username = null;
- foreach ($xrd->links as $link) {
- if ($link['rel'] == 'http://apinamespace.org/atom' &&
- $link['type'] == 'application/atomsvc+xml') {
- $svcDocUrl = $link['href'];
- if (!empty($link['property'])) {
- foreach ($link['property'] as $property) {
- if ($property['type'] == 'http://apinamespace.org/atom/username') {
- $username = $property['value'];
- break;
- }
- }
- }
+ $link = $xrd->links->get('http://apinamespace.org/atom', 'application/atomsvc+xml');
+ if (!is_null($link)) {
+ $svcDocUrl = $link->href;
+ if (isset($link['http://apinamespace.org/atom/username'])) {
+ $username = $link['http://apinamespace.org/atom/username'];
break;
}
}
+++ /dev/null
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, Inc.
- *
- * Use Hammer discovery stack to find out interesting things about an URI
- *
- * 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/>.
- *
- * @category Discovery
- * @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/
- */
-
-if (!defined('STATUSNET')) {
- exit(1);
-}
-
-/**
- * This class implements LRDD-based service discovery based on the "Hammer Draft"
- * (including webfinger)
- *
- * @category Discovery
- * @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/
- *
- * @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();
-
- /**
- * Constructor for a discovery object
- *
- * Registers different discovery methods.
- *
- * @return Discovery this
- */
-
- public function __construct()
- {
- $this->registerMethod('Discovery_LRDD_Host_Meta');
- $this->registerMethod('Discovery_LRDD_Link_Header');
- $this->registerMethod('Discovery_LRDD_Link_HTML');
- }
-
- /**
- * Register a discovery class
- *
- * @param string $class Class name
- *
- * @return void
- */
- 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.
- *
- * @param string $user_id User ID to normalize
- *
- * @return string normalized acct: or http(s)?: URI
- */
- 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;
- }
-
- /**
- * Determine if a string is a Webfinger ID
- *
- * Webfinger IDs look like foo@example.com or acct:foo@example.com
- *
- * @param string $user_id ID to check
- *
- * @return boolean true if $user_id is a Webfinger, else false
- */
- public static function isWebfinger($user_id)
- {
- $uri = Discovery::normalize($user_id);
-
- return (substr($uri, 0, 5) == 'acct:');
- }
-
- /**
- * Given a user ID, return the first available XRD
- *
- * @param string $id User ID URI
- *
- * @return XRD XRD object for the user
- */
- 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 (!empty($link['template'])) {
- $xrd_uri = Discovery::applyTemplate($link['template'], $uri);
- } else {
- $xrd_uri = $link['href'];
- }
-
- $xrd = $this->fetchXrd($xrd_uri);
- if ($xrd) {
- return $xrd;
- }
- }
- }
-
- // TRANS: Exception. %s is an ID.
- throw new Exception(sprintf(_('Unable to find services for %s.'), $id));
- }
-
- /**
- * Given an array of links, returns the matching service
- *
- * @param array $links Links to check
- * @param string $service Service to find
- *
- * @return array $link assoc array representing the link
- */
- public static function getService($links, $service)
- {
- if (!is_array($links)) {
- return false;
- }
-
- foreach ($links as $link) {
- if ($link['rel'] == $service) {
- return $link;
- }
- }
- }
-
- /**
- * Apply a template using an ID
- *
- * Replaces {uri} in template string with the ID given.
- *
- * @param string $template Template to match
- * @param string $id User ID to replace with
- *
- * @return string replaced values
- */
- public static function applyTemplate($template, $id)
- {
- $template = str_replace('{uri}', urlencode($id), $template);
-
- return $template;
- }
-
- /**
- * Fetch an XRD file and parse
- *
- * @param string $url URL of the XRD
- *
- * @return XRD object representing the XRD file
- */
- 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());
- }
-}
-
-/**
- * Abstract interface for discovery
- *
- * Objects that implement this interface can retrieve an array of
- * XRD links for the URI.
- *
- * @category Discovery
- * @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/
- */
-interface Discovery_LRDD
-{
- /**
- * Discover interesting info about the URI
- *
- * @param string $uri URI to inquire about
- *
- * @return array Links in the XRD file
- */
- public function discover($uri);
-}
-
-/**
- * Implementation of discovery using host-meta file
- *
- * Discovers XRD file for a user by going to the organization's
- * host-meta file and trying to find a template for LRDD.
- *
- * @category Discovery
- * @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/
- */
-class Discovery_LRDD_Host_Meta implements Discovery_LRDD
-{
- /**
- * Discovery core method
- *
- * For Webfinger and HTTP URIs, fetch the host-meta file
- * and look for LRDD templates
- *
- * @param string $uri URI to inquire about
- *
- * @return array Links in the XRD file
- */
- public function discover($uri)
- {
- 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);
- }
-
- $url = 'http://'. $domain .'/.well-known/host-meta';
-
- $xrd = Discovery::fetchXrd($url);
-
- if ($xrd) {
- return $xrd->links;
- }
- }
-}
-
-/**
- * Implementation of discovery using HTTP Link header
- *
- * Discovers XRD file for a user by fetching the URL and reading any
- * Link: headers in the HTTP response.
- *
- * @category Discovery
- * @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/
- */
-class Discovery_LRDD_Link_Header implements Discovery_LRDD
-{
- /**
- * Discovery core method
- *
- * For HTTP IDs fetch the URL and look for Link headers.
- *
- * @param string $uri URI to inquire about
- *
- * @return array Links in the XRD file
- *
- * @todo fail out of Webfinger URIs faster
- */
- 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 array(Discovery_LRDD_Link_Header::parseHeader($link_header));
- }
-
- /**
- * Given a string or array of headers, returns XRD-like assoc array
- *
- * @param string|array $header string or array of strings for headers
- *
- * @return array Link header in XRD-like format
- */
- protected static function parseHeader($header)
- {
- $lh = new LinkHeader($header);
-
- return array('href' => $lh->href,
- 'rel' => $lh->rel,
- 'type' => $lh->type);
- }
-}
-
-/**
- * Implementation of discovery using HTML <link> element
- *
- * Discovers XRD file for a user by fetching the URL and reading any
- * <link> elements in the HTML response.
- *
- * @category Discovery
- * @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/
- */
-class Discovery_LRDD_Link_HTML implements Discovery_LRDD
-{
- /**
- * Discovery core method
- *
- * For HTTP IDs, fetch the URL and look for <link> elements
- * in the HTML response.
- *
- * @param string $uri URI to inquire about
- *
- * @return array Links in XRD-ish assoc array
- *
- * @todo fail out of Webfinger URIs faster
- */
- 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());
- }
-
- /**
- * Parse HTML and return <link> elements
- *
- * Given an HTML string, scans the string for <link> elements
- *
- * @param string $html HTML to scan
- *
- * @return array array of associative arrays in XRD-ish format
- */
- 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;
- }
-}
* @link http://status.net/
*/
-if (!defined('STATUSNET')) {
+if (!defined('GNUSOCIAL')) {
exit(1);
}
}
/**
- * Pulls up StatusNet's customized user-agent string, so services
+ * Pulls up GNU Social's customized user-agent string, so services
* we hit can track down the responsible software.
*
* @return string
--- /dev/null
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for an exception when a URL is invalid
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category Exception
+ * @package StatusNet
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2013 Free Software Foundation, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://status.net/
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+/**
+ * Class for an exception when a URL is invalid
+ *
+ * @category Exception
+ * @package GNUSocial
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://status.net/
+ */
+
+class InvalidUrlException extends ServerException
+{
+ public $url = null;
+
+ public function __construct($url)
+ {
+ $this->url = $url;
+ // We could log an entry here with the search parameters
+ parent::__construct(_('Invalid URL.'));
+ }
+}
+++ /dev/null
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, Inc.
- *
- * Parse HTTP response for interesting Link: headers
- *
- * 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/>.
- *
- * @category Discovery
- * @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/
- */
-
-if (!defined('STATUSNET')) {
- exit(1);
-}
-
-/**
- * Class to represent Link: headers in an HTTP response
- *
- * Since these are a fairly important part of Hammer-stack discovery, they're
- * reified and implemented here.
- *
- * @category Discovery
- * @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/
- *
- * @see Discovery
- */
-class LinkHeader
-{
- var $href;
- var $rel;
- var $type;
-
- /**
- * Initialize from a string
- *
- * @param string $str Link: header value
- *
- * @return LinkHeader self
- */
- 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);
- }
- }
- }
-
- /**
- * Given an HTTP response, return the requested Link: header
- *
- * @param HTTP_Request2_Response $response response to check
- * @param string $rel relationship to look for
- * @param string $type media type to look for
- *
- * @return LinkHeader discovered header, or null on failure
- */
- 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;
- }
-}
if (preg_match('/^(\w+)(Action|Form)$/', $cls, $type)) {
$type = array_map('strtolower', $type);
$file = "$basedir/{$type[2]}s/{$type[1]}.php";
- } else {
+ }
+ if (!file_exists($file)) {
$file = "$basedir/classes/{$cls}.php";
+ // library files can be put into subdirs ('_'->'/' conversion)
+ // such as LRDDMethod_WebFinger -> lib/lrddmethod/webfinger.php
if (!file_exists($file)) {
$type = strtolower($cls);
+ $type = str_replace('_', '/', $type);
$file = "$basedir/lib/{$type}.php";
}
}
$m->connect('main/xrds',
array('action' => 'publicxrds'));
- $m->connect('.well-known/host-meta',
- array('action' => 'hostmeta'));
- $m->connect('main/xrd',
- array('action' => 'userxrd'));
// settings
'Bookmark' => null,
'Event' => null,
'OpenID' => null,
+ 'LRDD' => null,
'Poll' => null,
'QnA' => null,
'SearchSub' => null,
'ExtendedProfile' => null,
'Geonames' => null,
'OStatus' => null,
+ 'WebFinger' => null,
))
),
'discovery' => array('cors' => true) // Allow Cross-Origin Resource Sharing for service discovery (host-meta, XRD, etc.)
'Directory' => null,
'Geonames' => null,
'OStatus' => null,
+ 'WebFinger' => null,
))
),
'discovery' => array('cors' => true) // Allow Cross-Origin Resource Sharing for service discovery (host-meta, XRD, etc.)
'OStatus' => null,
'TwitterBridge' => null,
'FacebookBridge' => null,
+ 'WebFinger' => null,
))
),
'discovery' => array('cors' => true) // Allow Cross-Origin Resource Sharing for service discovery (host-meta, XRD, etc.)
+++ /dev/null
-<?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/
- */
-class XRD
-{
- const XML_NS = 'http://www.w3.org/2000/xmlns/';
-
- const XRD_NS = 'http://docs.oasis-open.org/ns/xri/xrd-1.0';
-
- const HOST_META_NS = 'http://host-meta.net/xrd/1.0';
-
- public $expires;
-
- public $subject;
-
- public $host;
-
- public $alias = array();
-
- public $types = array();
-
- public $links = array();
-
- public static function parse($xml)
- {
- $xrd = new XRD();
-
- $dom = new DOMDocument();
-
- // Don't spew XML warnings to output
- $old = error_reporting();
- error_reporting($old & ~E_WARNING);
- $ok = $dom->loadXML($xml);
- error_reporting($old);
-
- if (!$ok) {
- // TRANS: Exception.
- throw new Exception(_('Invalid XML.'));
- }
- $xrd_element = $dom->getElementsByTagName('XRD')->item(0);
- if (!$xrd_element) {
- // TRANS: Exception.
- throw new Exception(_('Invalid XML, missing XRD root.'));
- }
-
- // Check for host-meta host
- $host = $xrd_element->getElementsByTagName('Host')->item(0);
- if ($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;
- break;
- case 'Subject':
- $xrd->subject = $node->nodeValue;
- break;
-
- case 'Alias':
- $xrd->alias[] = $node->nodeValue;
- break;
-
- case 'Link':
- $xrd->links[] = $xrd->parseLink($node);
- break;
-
- case 'Type':
- $xrd->types[] = $xrd->parseType($node);
- break;
-
- }
- }
- return $xrd;
- }
-
- public function toXML()
- {
- $xs = new XMLStringer();
-
- $xs->startXML();
- $xs->elementStart('XRD', array('xmlns' => XRD::XRD_NS));
-
- if ($this->host) {
- $xs->element('hm:Host', array('xmlns:hm' => XRD::HOST_META_NS), $this->host);
- }
-
- if ($this->expires) {
- $xs->element('Expires', null, $this->expires);
- }
-
- if ($this->subject) {
- $xs->element('Subject', null, $this->subject);
- }
-
- foreach ($this->alias as $alias) {
- $xs->element('Alias', null, $alias);
- }
-
- foreach ($this->links as $link) {
- $titles = array();
- $properties = array();
- if (isset($link['title'])) {
- $titles = $link['title'];
- unset($link['title']);
- }
- if (isset($link['property'])) {
- $properties = $link['property'];
- unset($link['property']);
- }
- $xs->elementStart('Link', $link);
- foreach ($titles as $title) {
- $xs->element('Title', null, $title);
- }
- foreach ($properties as $property) {
- $xs->element('Property',
- array('type' => $property['type']),
- $property['value']);
- }
- $xs->elementEnd('Link');
- }
-
- $xs->elementEnd('XRD');
-
- return $xs->getString();
- }
-
- function parseType($element)
- {
- return array();
- }
-
- function parseLink($element)
- {
- $link = array();
- $link['rel'] = $element->getAttribute('rel');
- $link['type'] = $element->getAttribute('type');
- $link['href'] = $element->getAttribute('href');
- $link['template'] = $element->getAttribute('template');
- foreach ($element->childNodes as $node) {
- if ($node instanceof DOMElement) {
- switch($node->tagName) {
- case 'Title':
- $link['title'][] = $node->nodeValue;
- break;
- case 'Property':
- $link['property'][] = array('type' => $node->getAttribute('type'),
- 'value' => $node->nodeValue);
- break;
- default:
- common_log(LOG_NOTICE, "Unexpected tag name {$node->tagName} found in XRD file.");
- }
- }
- }
-
- return $link;
- }
-}
+++ /dev/null
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, Inc.
- *
- * 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 OStatusPlugin
- * @maintainer James Walker <james@status.net>
- */
-
-if (!defined('STATUSNET')) {
- exit(1);
-}
-
-class XrdAction extends Action
-{
- 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 $uri;
-
- public $user;
-
- public $xrd;
-
- function handle()
- {
- $nick = $this->user->nickname;
- $profile = $this->user->getProfile();
-
- if (empty($this->xrd)) {
- $xrd = new XRD();
- } else {
- $xrd = $this->xrd;
- }
-
- if (empty($xrd->subject)) {
- $xrd->subject = self::normalize($this->uri);
- }
-
- if (Event::handle('StartXrdActionAliases', array(&$xrd, $this->user))) {
-
- // Possible aliases for the user
-
- $uris = array($this->user->uri, $profile->profileurl);
-
- // FIXME: Webfinger generation code should live somewhere on its own
-
- $path = common_config('site', 'path');
-
- if (empty($path)) {
- $uris[] = sprintf('acct:%s@%s', $nick, common_config('site', 'server'));
- }
-
- foreach ($uris as $uri) {
- if ($uri != $xrd->subject) {
- $xrd->alias[] = $uri;
- }
- }
-
- Event::handle('EndXrdActionAliases', array(&$xrd, $this->user));
- }
-
- if (Event::handle('StartXrdActionLinks', array(&$xrd, $this->user))) {
-
- $xrd->links[] = array('rel' => self::PROFILEPAGE,
- 'type' => 'text/html',
- 'href' => $profile->profileurl);
-
- // XFN
- $xrd->links[] = array('rel' => 'http://gmpg.org/xfn/11',
- 'type' => 'text/html',
- 'href' => $profile->profileurl);
- // FOAF
- $xrd->links[] = array('rel' => 'describedby',
- 'type' => 'application/rdf+xml',
- 'href' => common_local_url('foaf',
- array('nickname' => $nick)));
-
- $xrd->links[] = array('rel' => 'http://apinamespace.org/atom',
- 'type' => 'application/atomsvc+xml',
- 'href' => common_local_url('ApiAtomService', array('id' => $nick)),
- 'property' => array(array('type' => 'http://apinamespace.org/atom/username',
- 'value' => $nick)));
-
- if (common_config('site', 'fancy')) {
- $apiRoot = common_path('api/', true);
- } else {
- $apiRoot = common_path('index.php/api/', true);
- }
-
- $xrd->links[] = array('rel' => 'http://apinamespace.org/twitter',
- 'href' => $apiRoot,
- 'property' => array(array('type' => 'http://apinamespace.org/twitter/username',
- 'value' => $nick)));
-
- Event::handle('EndXrdActionLinks', array(&$xrd, $this->user));
- }
-
- if (common_config('discovery', 'cors')) {
- header('Access-Control-Allow-Origin: *');
- }
-
- header('Content-type: application/xrd+xml');
-
- print $xrd->toXML();
- }
-
- /**
- * 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 = self::normalize($user_id);
-
- return (substr($uri, 0, 5) == 'acct:');
- }
-
- /**
- * Is this action read-only?
- *
- * @param array $args other arguments
- *
- * @return boolean is read only action?
- */
- function isReadOnly($args)
- {
- return true;
- }
-}
}
function onStartHostMetaLinks(&$links) {
- $links[] = array('rel' => AccountManagerPlugin::AM_REL,
- 'href' => common_local_url('AccountManagementControlDocument'));
+ $links[] = new XML_XRD_Element_Link(AccountManagerPlugin::AM_REL,
+ common_local_url('AccountManagementControlDocument'));
}
function onStartShowHTML($action)
--- /dev/null
+StartDiscoveryMethodRegistration
+- $disco: Discovery object that accepts the registrations
+
+EndDiscoveryMethodRegistration: Register remote URI discovery methods
+- $disco: Discovery object that accepts the registrations
+
--- /dev/null
+<?php
+/*
+ * GNU Social - a federating social network
+ * Copyright (C) 2013, Free Software Foundation, Inc.
+ *
+ * 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/>.
+ */
+
+/**
+ * Implements Link-based Resource Descriptor Discovery based on RFC6415,
+ * Web Host Metadata, i.e. the predecessor to WebFinger resource discovery.
+ *
+ * @package GNUSocial
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+set_include_path(get_include_path() . PATH_SEPARATOR . __DIR__ . '/extlib/');
+
+class LRDDPlugin extends Plugin
+{
+ public function onAutoload($cls)
+ {
+ switch ($cls) {
+ case 'XML_XRD':
+ require_once __DIR__ . '/extlib/XML/XRD.php';
+ return false;
+ }
+
+ return parent::onAutoload($cls);
+ }
+ public function onStartDiscoveryMethodRegistration(Discovery $disco) {
+ $disco->registerMethod('LRDDMethod_WebFinger');
+ }
+
+ public function onEndDiscoveryMethodRegistration(Discovery $disco) {
+ $disco->registerMethod('LRDDMethod_HostMeta');
+ $disco->registerMethod('LRDDMethod_LinkHeader');
+ $disco->registerMethod('LRDDMethod_LinkHTML');
+ }
+
+ public function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'LRDD',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Mikael Nordfeldth',
+ 'homepage' => 'http://www.gnu.org/software/social/',
+ // TRANS: Plugin description.
+ 'rawdescription' => _m('Implements LRDD support for GNU Social.'));
+
+ return true;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+require_once 'XML/XRD/PropertyAccess.php';
+require_once 'XML/XRD/Element/Link.php';
+require_once 'XML/XRD/Loader.php';
+require_once 'XML/XRD/Serializer.php';
+
+/**
+ * Main class used to load XRD documents from string or file.
+ *
+ * After loading the file, access to links is possible with get() and getAll(),
+ * as well as foreach-iterating over the XML_XRD object.
+ *
+ * Property access is possible with getProperties() and array access (foreach)
+ * on the XML_XRD object.
+ *
+ * Verification that the subject/aliases match the requested URL can be done with
+ * describes().
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ */
+class XML_XRD extends XML_XRD_PropertyAccess implements IteratorAggregate
+{
+ /**
+ * XRD file/string loading dispatcher
+ *
+ * @var XML_XRD_Loader
+ */
+ public $loader;
+
+ /**
+ * XRD serializing dispatcher
+ *
+ * @var XML_XRD_Serializer
+ */
+ public $serializer;
+
+ /**
+ * XRD subject
+ *
+ * @var string
+ */
+ public $subject;
+
+ /**
+ * Array of subject alias strings
+ *
+ * @var array
+ */
+ public $aliases = array();
+
+ /**
+ * Array of link objects
+ *
+ * @var array
+ */
+ public $links = array();
+
+ /**
+ * Unix timestamp when the document expires.
+ * NULL when no expiry date set.
+ *
+ * @var integer|null
+ */
+ public $expires;
+
+ /**
+ * xml:id of the XRD document
+ *
+ * @var string|null
+ */
+ public $id;
+
+
+
+ /**
+ * Loads the contents of the given file.
+ *
+ * Note: Only use file type auto-detection for local files.
+ * Do not use it on remote files as the file gets requested several times.
+ *
+ * @param string $file Path to an XRD file
+ * @param string $type File type: xml or json, NULL for auto-detection
+ *
+ * @return void
+ *
+ * @throws XML_XRD_Loader_Exception When the file is invalid or cannot be
+ * loaded
+ */
+ public function loadFile($file, $type = null)
+ {
+ if (!isset($this->loader)) {
+ $this->loader = new XML_XRD_Loader($this);
+ }
+ return $this->loader->loadFile($file, $type);
+ }
+
+ /**
+ * Loads the contents of the given string
+ *
+ * @param string $str XRD string
+ * @param string $type File type: xml or json, NULL for auto-detection
+ *
+ * @return void
+ *
+ * @throws XML_XRD_Loader_Exception When the string is invalid or cannot be
+ * loaded
+ */
+ public function loadString($str, $type = null)
+ {
+ if (!isset($this->loader)) {
+ $this->loader = new XML_XRD_Loader($this);
+ }
+ return $this->loader->loadString($str, $type);
+ }
+
+ /**
+ * Checks if the XRD document describes the given URI.
+ *
+ * This should always be used to make sure the XRD file
+ * is the correct one for e.g. the given host, and not a copycat.
+ *
+ * Checks against the subject and aliases
+ *
+ * @param string $uri An URI that the document is expected to describe
+ *
+ * @return boolean True or false
+ */
+ public function describes($uri)
+ {
+ if ($this->subject == $uri) {
+ return true;
+ }
+ foreach ($this->aliases as $alias) {
+ if ($alias == $uri) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the link with highest priority for the given relation and type.
+ *
+ * @param string $rel Relation name
+ * @param string $type MIME Type
+ * @param boolean $typeFallback When true and no link with the given type
+ * could be found, the best link without a
+ * type will be returned
+ *
+ * @return XML_XRD_Element_Link Link object or NULL if none found
+ */
+ public function get($rel, $type = null, $typeFallback = true)
+ {
+ $links = $this->getAll($rel, $type, $typeFallback);
+ if (count($links) == 0) {
+ return null;
+ }
+
+ return $links[0];
+ }
+
+
+ /**
+ * Get all links with the given relation and type, highest priority first.
+ *
+ * @param string $rel Relation name
+ * @param string $type MIME Type
+ * @param boolean $typeFallback When true and no link with the given type
+ * could be found, the best link without a
+ * type will be returned
+ *
+ * @return array Array of XML_XRD_Element_Link objects
+ */
+ public function getAll($rel, $type = null, $typeFallback = true)
+ {
+ $links = array();
+ $exactType = false;
+ foreach ($this->links as $link) {
+ if ($link->rel == $rel
+ && ($type === null || $link->type == $type
+ || $typeFallback && $link->type === null)
+ ) {
+ $links[] = $link;
+ $exactType |= $typeFallback && $type !== null
+ && $link->type == $type;
+ }
+ }
+ if ($exactType) {
+ //remove all links without type
+ $exactlinks = array();
+ foreach ($links as $link) {
+ if ($link->type !== null) {
+ $exactlinks[] = $link;
+ }
+ }
+ $links = $exactlinks;
+ }
+ return $links;
+ }
+
+ /**
+ * Return the iterator object to loop over the links
+ *
+ * Part of the IteratorAggregate interface
+ *
+ * @return Traversable Iterator for the links
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->links);
+ }
+
+ /**
+ * Converts this XRD object to XML or JSON.
+ *
+ * @param string $type Serialization type: xml or json
+ *
+ * @return string Generated content
+ */
+ public function to($type)
+ {
+ if (!isset($this->serializer)) {
+ $this->serializer = new XML_XRD_Serializer($this);
+ }
+ return $this->serializer->to($type);
+ }
+
+ /**
+ * Converts this XRD object to XML.
+ *
+ * @return string Generated XML
+ *
+ * @deprecated use to('xml')
+ */
+ public function toXML()
+ {
+ return $this->to('xml');
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+require_once 'XML/XRD/PropertyAccess.php';
+
+/**
+ * Link element in a XRD file. Attribute access via object properties.
+ *
+ * Retrieving the title of a link is possible with the getTitle() convenience
+ * method.
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ */
+class XML_XRD_Element_Link extends XML_XRD_PropertyAccess
+{
+ /**
+ * Link relation
+ *
+ * @var string
+ */
+ public $rel;
+
+ /**
+ * Link type (MIME type)
+ *
+ * @var string
+ */
+ public $type;
+
+ /**
+ * Link URL
+ *
+ * @var string
+ */
+ public $href;
+
+ /**
+ * Link URL template
+ *
+ * @var string
+ */
+ public $template;
+
+ /**
+ * Array of key-value pairs: Key is the language, value the title
+ *
+ * @var array
+ */
+ public $titles = array();
+
+
+
+ /**
+ * Create a new instance and load data from the XML element
+ *
+ * @param string $relOrXml string with the relation name/URL
+ * @param string $href HREF value
+ * @param string $type Type value
+ * @param boolean $isTemplate When set to true, the $href is
+ * used as template
+ */
+ public function __construct(
+ $rel = null, $href = null, $type = null, $isTemplate = false
+ ) {
+ $this->rel = $rel;
+ if ($isTemplate) {
+ $this->template = $href;
+ } else {
+ $this->href = $href;
+ }
+ $this->type = $type;
+ }
+
+ /**
+ * Returns the title of the link in the given language.
+ * If the language is not available, the first title without the language
+ * is returned. If no such one exists, the first title is returned.
+ *
+ * @param string $lang 2-letter language name
+ *
+ * @return string|null Link title
+ */
+ public function getTitle($lang = null)
+ {
+ if (count($this->titles) == 0) {
+ return null;
+ }
+
+ if ($lang == null) {
+ return reset($this->titles);
+ }
+
+ if (isset($this->titles[$lang])) {
+ return $this->titles[$lang];
+ }
+ if (isset($this->titles[''])) {
+ return $this->titles[''];
+ }
+
+ //return first
+ return reset($this->titles);
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+/**
+ * Property element in a XRD document.
+ *
+ * The <XRD> root element as well as <Link> tags may have <Property> children.
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ */
+class XML_XRD_Element_Property
+{
+ /**
+ * Value of the property.
+ *
+ * @var string|null
+ */
+ public $value;
+
+ /**
+ * Type of the propery.
+ *
+ * @var string
+ */
+ public $type;
+
+ /**
+ * Create a new instance
+ *
+ * @param string $type String representing the property type
+ * @param string $value Value of the property, may be NULL
+ */
+ public function __construct($type = null, $value = null)
+ {
+ $this->type = $type;
+ $this->value = $value;
+ }
+}
+
+?>
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+/**
+ * Base exception interface for all XML_XRD related exceptions.
+ * With that interface, it is possible to catch all XML_XRD exceptions
+ * with a single catch statement.
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ */
+interface XML_XRD_Exception
+{
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+require_once 'XML/XRD/Loader/Exception.php';
+
+/**
+ * File/string loading dispatcher.
+ * Loads the correct loader for the type of XRD file (XML or JSON).
+ * Also provides type auto-detection.
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ */
+class XML_XRD_Loader
+{
+ public function __construct(XML_XRD $xrd)
+ {
+ $this->xrd = $xrd;
+ }
+
+ /**
+ * Loads the contents of the given file.
+ *
+ * Note: Only use file type auto-detection for local files.
+ * Do not use it on remote files as the file gets requested several times.
+ *
+ * @param string $file Path to an XRD file
+ * @param string $type File type: xml or json, NULL for auto-detection
+ *
+ * @return void
+ *
+ * @throws XML_XRD_Loader_Exception When the file is invalid or cannot be
+ * loaded
+ */
+ public function loadFile($file, $type = null)
+ {
+ if ($type === null) {
+ $type = $this->detectTypeFromFile($file);
+ }
+ $loader = $this->getLoader($type);
+ $loader->loadFile($file);
+ }
+
+ /**
+ * Loads the contents of the given string
+ *
+ * @param string $str XRD string
+ * @param string $type File type: xml or json, NULL for auto-detection
+ *
+ * @return void
+ *
+ * @throws XML_XRD_Loader_Exception When the string is invalid or cannot be
+ * loaded
+ */
+ public function loadString($str, $type = null)
+ {
+ if ($type === null) {
+ $type = $this->detectTypeFromString($str);
+ }
+ $loader = $this->getLoader($type);
+ $loader->loadString($str);
+ }
+
+ /**
+ * Creates a XRD loader object for the given type
+ *
+ * @param string $type File type: xml or json
+ *
+ * @return XML_XRD_Loader
+ */
+ protected function getLoader($type)
+ {
+ $class = 'XML_XRD_Loader_' . strtoupper($type);
+ $file = str_replace('_', '/', $class) . '.php';
+ include_once $file;
+ if (class_exists($class)) {
+ return new $class($this->xrd);
+ }
+
+ throw new XML_XRD_Loader_Exception(
+ 'No loader for XRD type "' . $type . '"',
+ XML_XRD_Loader_Exception::NO_LOADER
+ );
+ }
+
+ /**
+ * Tries to detect the file type (xml or json) from the file content
+ *
+ * @param string $file File name to check
+ *
+ * @return string File type ('xml' or 'json')
+ *
+ * @throws XML_XRD_Loader_Exception When opening the file fails.
+ */
+ public function detectTypeFromFile($file)
+ {
+ if (!file_exists($file)) {
+ throw new XML_XRD_Loader_Exception(
+ 'Error loading XRD file: File does not exist',
+ XML_XRD_Loader_Exception::OPEN_FILE
+ );
+ }
+ $handle = fopen($file, 'r');
+ if (!$handle) {
+ throw new XML_XRD_Loader_Exception(
+ 'Cannot open file to determine type',
+ XML_XRD_Loader_Exception::OPEN_FILE
+ );
+ }
+
+ $str = (string)fgets($handle, 10);
+ fclose($handle);
+ return $this->detectTypeFromString($str);
+ }
+
+ /**
+ * Tries to detect the file type from the content of the file
+ *
+ * @param string $str Content of XRD file
+ *
+ * @return string File type ('xml' or 'json')
+ *
+ * @throws XML_XRD_Loader_Exception When the type cannot be detected
+ */
+ public function detectTypeFromString($str)
+ {
+ if (substr($str, 0, 1) == '{') {
+ return 'json';
+ } else if (substr($str, 0, 5) == '<?xml') {
+ return 'xml';
+ }
+
+ throw new XML_XRD_Loader_Exception(
+ 'Detecting file type failed',
+ XML_XRD_Loader_Exception::DETECT_TYPE
+ );
+ }
+
+
+}
+
+?>
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+require_once 'XML/XRD/Exception.php';
+
+/**
+ * XML_XRD exception that's thrown when loading the XRD fails.
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ */
+class XML_XRD_Loader_Exception extends Exception implements XML_XRD_Exception
+{
+ /**
+ * The document namespace is not the XRD 1.0 namespace
+ */
+ const DOC_NS = 10;
+
+ /**
+ * The document root element is not XRD
+ */
+ const DOC_ROOT = 11;
+
+ /**
+ * Error loading the XML|JSON file|string
+ */
+ const LOAD = 12;
+
+ /**
+ * Unsupported XRD file/string type (no loader)
+ */
+ const NO_LOADER = 13;
+
+ /**
+ * Error opening file
+ */
+ const OPEN_FILE = 14;
+
+ /**
+ * Detecting the file type failed
+ */
+ const DETECT_TYPE = 20;
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+/**
+ * Loads XRD data from a JSON file
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ */
+class XML_XRD_Loader_JSON
+{
+ /**
+ * Data storage the XML data get loaded into
+ *
+ * @var XML_XRD
+ */
+ protected $xrd;
+
+
+
+ /**
+ * Init object with xrd object
+ *
+ * @param XML_XRD $xrd Data storage the JSON data get loaded into
+ */
+ public function __construct(XML_XRD $xrd)
+ {
+ $this->xrd = $xrd;
+ }
+
+ /**
+ * Loads the contents of the given file
+ *
+ * @param string $file Path to an JRD file
+ *
+ * @return void
+ *
+ * @throws XML_XRD_Loader_Exception When the JSON is invalid or cannot be
+ * loaded
+ */
+ public function loadFile($file)
+ {
+ $json = file_get_contents($file);
+ if ($json === false) {
+ throw new XML_XRD_Loader_Exception(
+ 'Error loading JRD file: ' . $file,
+ XML_XRD_Loader_Exception::LOAD
+ );
+ }
+ return $this->loadString($json);
+ }
+
+ /**
+ * Loads the contents of the given string
+ *
+ * @param string $json JSON string
+ *
+ * @return void
+ *
+ * @throws XML_XRD_Loader_Exception When the JSON is invalid or cannot be
+ * loaded
+ */
+ public function loadString($json)
+ {
+ if ($json == '') {
+ throw new XML_XRD_Loader_Exception(
+ 'Error loading JRD: string empty',
+ XML_XRD_Loader_Exception::LOAD
+ );
+ }
+
+ $obj = json_decode($json);
+ if ($obj !== null) {
+ return $this->load($obj);
+ }
+
+ $constants = get_defined_constants(true);
+ $json_errors = array();
+ foreach ($constants['json'] as $name => $value) {
+ if (!strncmp($name, 'JSON_ERROR_', 11)) {
+ $json_errors[$value] = $name;
+ }
+ }
+ throw new XML_XRD_Loader_Exception(
+ 'Error loading JRD: ' . $json_errors[json_last_error()],
+ XML_XRD_Loader_Exception::LOAD
+ );
+ }
+
+ /**
+ * Loads the JSON object into the classes' data structures
+ *
+ * @param object $j JSON object containing the whole JSON document
+ *
+ * @return void
+ */
+ public function load(stdClass $j)
+ {
+ if (isset($j->subject)) {
+ $this->xrd->subject = (string)$j->subject;
+ }
+ if (isset($j->aliases)) {
+ foreach ($j->aliases as $jAlias) {
+ $this->xrd->aliases[] = (string)$jAlias;
+ }
+ }
+
+ if (isset($j->links)) {
+ foreach ($j->links as $jLink) {
+ $this->xrd->links[] = $this->loadLink($jLink);
+ }
+ }
+
+ $this->loadProperties($this->xrd, $j);
+
+ if (isset($j->expires)) {
+ $this->xrd->expires = strtotime($j->expires);
+ }
+ }
+
+ /**
+ * Loads the Property elements from XML
+ *
+ * @param object $store Data store where the properties get stored
+ * @param object $j JSON element with "properties" variable
+ *
+ * @return boolean True when all went well
+ */
+ protected function loadProperties(
+ XML_XRD_PropertyAccess $store, stdClass $j
+ ) {
+ if (!isset($j->properties)) {
+ return true;
+ }
+
+ foreach ($j->properties as $type => $jProp) {
+ $store->properties[] = new XML_XRD_Element_Property(
+ $type, (string)$jProp
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Create a link element object from XML element
+ *
+ * @param object $j JSON link object
+ *
+ * @return XML_XRD_Element_Link Created link object
+ */
+ protected function loadLink(stdClass $j)
+ {
+ $link = new XML_XRD_Element_Link();
+ foreach (array('rel', 'type', 'href', 'template') as $var) {
+ if (isset($j->$var)) {
+ $link->$var = (string)$j->$var;
+ }
+ }
+
+ if (isset($j->titles)) {
+ foreach ($j->titles as $lang => $jTitle) {
+ if (!isset($link->titles[$lang])) {
+ $link->titles[$lang] = (string)$jTitle;
+ }
+ }
+ }
+ $this->loadProperties($link, $j);
+
+ return $link;
+ }
+}
+?>
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+/**
+ * Loads XRD data from an XML file
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ */
+class XML_XRD_Loader_XML
+{
+ /**
+ * Data storage the XML data get loaded into
+ *
+ * @var XML_XRD
+ */
+ protected $xrd;
+
+ /**
+ * XRD 1.0 namespace
+ */
+ const NS_XRD = 'http://docs.oasis-open.org/ns/xri/xrd-1.0';
+
+
+
+ /**
+ * Init object with xrd object
+ *
+ * @param XML_XRD $xrd Data storage the XML data get loaded into
+ */
+ public function __construct(XML_XRD $xrd)
+ {
+ $this->xrd = $xrd;
+ }
+
+ /**
+ * Loads the contents of the given file
+ *
+ * @param string $file Path to an XRD file
+ *
+ * @return void
+ *
+ * @throws XML_XRD_Loader_Exception When the XML is invalid or cannot be
+ * loaded
+ */
+ public function loadFile($file)
+ {
+ $old = libxml_use_internal_errors(true);
+ $x = simplexml_load_file($file);
+ libxml_use_internal_errors($old);
+ if ($x === false) {
+ throw new XML_XRD_Loader_Exception(
+ 'Error loading XML file: ' . libxml_get_last_error()->message,
+ XML_XRD_Loader_Exception::LOAD
+ );
+ }
+ return $this->load($x);
+ }
+
+ /**
+ * Loads the contents of the given string
+ *
+ * @param string $xml XML string
+ *
+ * @return void
+ *
+ * @throws XML_XRD_Loader_Exception When the XML is invalid or cannot be
+ * loaded
+ */
+ public function loadString($xml)
+ {
+ if ($xml == '') {
+ throw new XML_XRD_Loader_Exception(
+ 'Error loading XML string: string empty',
+ XML_XRD_Loader_Exception::LOAD
+ );
+ }
+ $old = libxml_use_internal_errors(true);
+ $x = simplexml_load_string($xml);
+ libxml_use_internal_errors($old);
+ if ($x === false) {
+ throw new XML_XRD_Loader_Exception(
+ 'Error loading XML string: ' . libxml_get_last_error()->message,
+ XML_XRD_Loader_Exception::LOAD
+ );
+ }
+ return $this->load($x);
+ }
+
+ /**
+ * Loads the XML element into the classes' data structures
+ *
+ * @param object $x XML element containing the whole XRD document
+ *
+ * @return void
+ *
+ * @throws XML_XRD_Loader_Exception When the XML is invalid
+ */
+ public function load(SimpleXMLElement $x)
+ {
+ $ns = $x->getDocNamespaces();
+ if ($ns[''] !== self::NS_XRD) {
+ throw new XML_XRD_Loader_Exception(
+ 'Wrong document namespace', XML_XRD_Loader_Exception::DOC_NS
+ );
+ }
+ if ($x->getName() != 'XRD') {
+ throw new XML_XRD_Loader_Exception(
+ 'XML root element is not "XRD"', XML_XRD_Loader_Exception::DOC_ROOT
+ );
+ }
+
+ if (isset($x->Subject)) {
+ $this->xrd->subject = (string)$x->Subject;
+ }
+ foreach ($x->Alias as $xAlias) {
+ $this->xrd->aliases[] = (string)$xAlias;
+ }
+
+ foreach ($x->Link as $xLink) {
+ $this->xrd->links[] = $this->loadLink($xLink);
+ }
+
+ $this->loadProperties($this->xrd, $x);
+
+ if (isset($x->Expires)) {
+ $this->xrd->expires = strtotime($x->Expires);
+ }
+
+ $xmlAttrs = $x->attributes('http://www.w3.org/XML/1998/namespace');
+ if (isset($xmlAttrs['id'])) {
+ $this->xrd->id = (string)$xmlAttrs['id'];
+ }
+ }
+
+ /**
+ * Loads the Property elements from XML
+ *
+ * @param object $store Data store where the properties get stored
+ * @param object $x XML element
+ *
+ * @return boolean True when all went well
+ */
+ protected function loadProperties(
+ XML_XRD_PropertyAccess $store, SimpleXMLElement $x
+ ) {
+ foreach ($x->Property as $xProp) {
+ $store->properties[] = $this->loadProperty($xProp);
+ }
+ }
+
+ /**
+ * Create a link element object from XML element
+ *
+ * @param object $x XML link element
+ *
+ * @return XML_XRD_Element_Link Created link object
+ */
+ protected function loadLink(SimpleXMLElement $x)
+ {
+ $link = new XML_XRD_Element_Link();
+ foreach (array('rel', 'type', 'href', 'template') as $var) {
+ if (isset($x[$var])) {
+ $link->$var = (string)$x[$var];
+ }
+ }
+
+ foreach ($x->Title as $xTitle) {
+ $xmlAttrs = $xTitle->attributes('http://www.w3.org/XML/1998/namespace');
+ $lang = '';
+ if (isset($xmlAttrs['lang'])) {
+ $lang = (string)$xmlAttrs['lang'];
+ }
+ if (!isset($link->titles[$lang])) {
+ $link->titles[$lang] = (string)$xTitle;
+ }
+ }
+ $this->loadProperties($link, $x);
+
+ return $link;
+ }
+
+ /**
+ * Create a property element object from XML element
+ *
+ * @param object $x XML property element
+ *
+ * @return XML_XRD_Element_Property Created link object
+ */
+ protected function loadProperty(SimpleXMLElement $x)
+ {
+ $prop = new XML_XRD_Element_Property();
+ if (isset($x['type'])) {
+ $prop->type = (string)$x['type'];
+ }
+ $s = (string)$x;
+ if ($s != '') {
+ $prop->value = $s;
+ }
+
+ return $prop;
+ }
+}
+?>
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+require_once 'XML/XRD/Exception.php';
+
+/**
+ * XML_XRD exception that's thrown when something is not supported
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ */
+class XML_XRD_LogicException extends LogicException implements XML_XRD_Exception
+{
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+require_once 'XML/XRD/LogicException.php';
+require_once 'XML/XRD/Element/Property.php';
+
+/**
+ * Provides ArrayAccess to extending classes (XML_XRD and XML_XRD_Element_Link).
+ *
+ * By extending PropertyAccess, access to properties is possible with
+ * "$object['propertyType']" array access notation.
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ */
+abstract class XML_XRD_PropertyAccess implements ArrayAccess
+{
+
+ /**
+ * Array of property objects
+ *
+ * @var array
+ */
+ public $properties = array();
+
+
+ /**
+ * Check if the property with the given type exists
+ *
+ * Part of the ArrayAccess interface
+ *
+ * @param string $type Property type to check for
+ *
+ * @return boolean True if it exists
+ */
+ public function offsetExists($type)
+ {
+ foreach ($this->properties as $prop) {
+ if ($prop->type == $type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the highest ranked property with the given type
+ *
+ * Part of the ArrayAccess interface
+ *
+ * @param string $type Property type to check for
+ *
+ * @return string Property value or NULL if empty
+ */
+ public function offsetGet($type)
+ {
+ foreach ($this->properties as $prop) {
+ if ($prop->type == $type) {
+ return $prop->value;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Not implemented.
+ *
+ * Part of the ArrayAccess interface
+ *
+ * @param string $type Property type to check for
+ * @param string $value New property value
+ *
+ * @return void
+ *
+ * @throws XML_XRD_LogicException Always
+ */
+ public function offsetSet($type, $value)
+ {
+ throw new XML_XRD_LogicException('Changing properties not implemented');
+ }
+
+ /**
+ * Not implemented.
+ *
+ * Part of the ArrayAccess interface
+ *
+ * @param string $type Property type to check for
+ *
+ * @return void
+ *
+ * @throws XML_XRD_LogicException Always
+ */
+ public function offsetUnset($type)
+ {
+ throw new XML_XRD_LogicException('Changing properties not implemented');
+ }
+
+ /**
+ * Get all properties with the given type
+ *
+ * @param string $type Property type to filter by
+ *
+ * @return array Array of XML_XRD_Element_Property objects
+ */
+ public function getProperties($type = null)
+ {
+ if ($type === null) {
+ return $this->properties;
+ }
+ $properties = array();
+ foreach ($this->properties as $prop) {
+ if ($prop->type == $type) {
+ $properties[] = $prop;
+ }
+ }
+ return $properties;
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+require_once 'XML/XRD/Serializer/Exception.php';
+
+/**
+ * Serialization dispatcher - loads the correct serializer for saving XRD data.
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ */
+class XML_XRD_Serializer
+{
+ /**
+ * XRD data storage
+ *
+ * @var XML_XRD
+ */
+ protected $xrd;
+
+ /**
+ * Init object with xrd object
+ *
+ * @param XML_XRD $xrd Data storage the data are fetched from
+ */
+ public function __construct(XML_XRD $xrd)
+ {
+ $this->xrd = $xrd;
+ }
+
+ /**
+ * Convert the XRD data into a string of the given type
+ *
+ * @param string $type File type: xml or json
+ *
+ * @return string Serialized data
+ */
+ public function to($type)
+ {
+ return (string)$this->getSerializer($type);
+ }
+
+ /**
+ * Creates a XRD loader object for the given type
+ *
+ * @param string $type File type: xml or json
+ *
+ * @return XML_XRD_Loader
+ */
+ protected function getSerializer($type)
+ {
+ $class = 'XML_XRD_Serializer_' . strtoupper($type);
+ $file = str_replace('_', '/', $class) . '.php';
+ include_once $file;
+ if (class_exists($class)) {
+ return new $class($this->xrd);
+ }
+
+ throw new XML_XRD_Serializer_Exception(
+ 'No serializer for type "' . $type . '"',
+ XML_XRD_Loader_Exception::NO_LOADER
+ );
+ }
+}
+?>
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+require_once 'XML/XRD/Exception.php';
+
+/**
+ * XML_XRD exception that's thrown when saving an XRD file fails.
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ */
+class XML_XRD_Serializer_Exception extends Exception implements XML_XRD_Exception
+{
+}
+?>
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+/**
+ * Generate JSON from a XML_XRD object (for JRD files).
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ * @link http://tools.ietf.org/html/rfc6415#appendix-A
+ */
+class XML_XRD_Serializer_JSON
+{
+ protected $xrd;
+
+ /**
+ * Create new instance
+ *
+ * @param XML_XRD $xrd XRD instance to convert to JSON
+ */
+ public function __construct(XML_XRD $xrd)
+ {
+ $this->xrd = $xrd;
+ }
+
+ /**
+ * Generate JSON.
+ *
+ * @return string JSON code
+ */
+ public function __toString()
+ {
+ $o = new stdClass();
+ if ($this->xrd->expires !== null) {
+ $o->expires = gmdate('Y-m-d\TH:i:s\Z', $this->xrd->expires);
+ }
+ if ($this->xrd->subject !== null) {
+ $o->subject = $this->xrd->subject;
+ }
+ foreach ($this->xrd->aliases as $alias) {
+ $o->aliases[] = $alias;
+ }
+ foreach ($this->xrd->properties as $property) {
+ $o->properties[$property->type] = $property->value;
+ }
+ $o->links = array();
+ foreach ($this->xrd->links as $link) {
+ $lid = count($o->links);
+ $o->links[$lid] = new stdClass();
+ if ($link->rel) {
+ $o->links[$lid]->rel = $link->rel;
+ }
+ if ($link->type) {
+ $o->links[$lid]->type = $link->type;
+ }
+ if ($link->href) {
+ $o->links[$lid]->href = $link->href;
+ }
+ if ($link->template !== null && $link->href === null) {
+ $o->links[$lid]->template = $link->template;
+ }
+
+ foreach ($link->titles as $lang => $value) {
+ if ($lang == null) {
+ $lang = 'default';
+ }
+ $o->links[$lid]->titles[$lang] = $value;
+ }
+ foreach ($link->properties as $property) {
+ $o->links[$lid]->properties[$property->type] = $property->value;
+ }
+ }
+ if (count($o->links) == 0) {
+ unset($o->links);
+ }
+
+ return json_encode($o);
+ }
+}
+
+?>
--- /dev/null
+<?php
+/**
+ * Part of XML_XRD
+ *
+ * PHP version 5
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/XML_XRD
+ */
+
+/**
+ * Generate XML from a XML_XRD object.
+ *
+ * @category XML
+ * @package XML_XRD
+ * @author Christian Weiske <cweiske@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/XML_XRD
+ */
+class XML_XRD_Serializer_XML
+{
+ protected $xrd;
+
+ /**
+ * Create new instance
+ *
+ * @param XML_XRD $xrd XRD instance to convert to XML
+ */
+ public function __construct(XML_XRD $xrd)
+ {
+ $this->xrd = $xrd;
+ }
+
+ /**
+ * Generate XML.
+ *
+ * @return string Full XML code
+ */
+ public function __toString()
+ {
+ $hasXsi = false;
+ $x = new XMLWriter();
+ $x->openMemory();
+ //no encoding means UTF-8
+ //http://www.w3.org/TR/2008/REC-xml-20081126/#sec-guessing-no-ext-info
+ $x->startDocument('1.0', 'UTF-8');
+ $x->setIndent(true);
+ $x->startElement('XRD');
+ $x->writeAttribute('xmlns', 'http://docs.oasis-open.org/ns/xri/xrd-1.0');
+ $x->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
+ if ($this->xrd->id) {
+ $x->writeAttribute('xml:id', $this->xrd->id);
+ }
+
+ if ($this->xrd->expires !== null) {
+ $x->writeElement(
+ 'Expires', gmdate('Y-m-d\TH:i:s\Z', $this->xrd->expires)
+ );
+ }
+ if ($this->xrd->subject !== null) {
+ $x->writeElement('Subject', $this->xrd->subject);
+ }
+ foreach ($this->xrd->aliases as $alias) {
+ $x->writeElement('Alias', $alias);
+ }
+ foreach ($this->xrd->properties as $property) {
+ $this->writeProperty($x, $property, $hasXsi);
+ }
+
+ foreach ($this->xrd->links as $link) {
+ $x->startElement('Link');
+ $x->writeAttribute('rel', $link->rel);
+ if ($link->type !== null) {
+ $x->writeAttribute('type', $link->type);
+ }
+ if ($link->href !== null) {
+ $x->writeAttribute('href', $link->href);
+ }
+ //template only when no href
+ if ($link->template !== null && $link->href === null) {
+ $x->writeAttribute('template', $link->template);
+ }
+
+ foreach ($link->titles as $lang => $value) {
+ $x->startElement('Title');
+ if ($lang) {
+ $x->writeAttribute('xml:lang', $lang);
+ }
+ $x->text($value);
+ $x->endElement();
+ }
+ foreach ($link->properties as $property) {
+ $this->writeProperty($x, $property, $hasXsi);
+ }
+ $x->endElement();
+ }
+
+ $x->endElement();
+ $x->endDocument();
+ $s = $x->flush();
+ if (!$hasXsi) {
+ $s = str_replace(
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"', '', $s
+ );
+ }
+ return $s;
+ }
+
+ /**
+ * Write a property in the XMLWriter stream output
+ *
+ * @param XMLWriter $x Writer object to write to
+ * @param XML_XRD_Element_Property $property Property to write
+ * @param boolean &$hasXsi If an xsi: attribute is used
+ *
+ * @return void
+ */
+ protected function writeProperty(
+ XMLWriter $x, XML_XRD_Element_Property $property, &$hasXsi
+ ) {
+ $x->startElement('Property');
+ $x->writeAttribute('type', $property->type);
+ if ($property->value === null) {
+ $x->writeAttribute('xsi:nil', 'true');
+ $hasXsi = true;
+ } else {
+ $x->text($property->value);
+ }
+ $x->endElement();
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * This class performs lookups based on methods implemented in separate
+ * classes, where a resource uri is given. Examples are WebFinger (RFC7033)
+ * and the LRDD (Link-based Resource Descriptor Discovery) in RFC6415.
+ *
+ * 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/>.
+ *
+ * @category Discovery
+ * @package GNUSocial
+ * @author James Walker <james@status.net>
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2010 StatusNet, Inc.
+ * @copyright 2013 Free Software Foundation, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://www.gnu.org/software/social/
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class Discovery
+{
+ const LRDD_REL = 'lrdd';
+ const UPDATESFROM = 'http://schemas.google.com/g/2010#updates-from';
+ const HCARD = 'http://microformats.org/profile/hcard';
+
+ const JRD_MIMETYPE_OLD = 'application/json'; // RFC6415 uses this
+ const JRD_MIMETYPE = 'application/jrd+json';
+ const XRD_MIMETYPE = 'application/xrd+xml';
+
+ public $methods = array();
+
+ /**
+ * Constructor for a discovery object
+ *
+ * Registers different discovery methods.
+ *
+ * @return Discovery this
+ */
+
+ public function __construct()
+ {
+ if (Event::handle('StartDiscoveryMethodRegistration', array($this))) {
+ Event::handle('EndDiscoveryMethodRegistration', array($this));
+ }
+ }
+
+ public static function supportedMimeTypes()
+ {
+ return array('json'=>self::JRD_MIMETYPE,
+ 'jsonold'=>self::JRD_MIMETYPE_OLD,
+ 'xml'=>self::XRD_MIMETYPE);
+ }
+
+ /**
+ * Register a discovery class
+ *
+ * @param string $class Class name
+ *
+ * @return void
+ */
+ public function registerMethod($class)
+ {
+ $this->methods[] = $class;
+ }
+
+ /**
+ * Given a user ID, return the first available resource descriptor
+ *
+ * @param string $id User ID URI
+ *
+ * @return XML_XRD object for the resource descriptor of the id
+ */
+ public function lookup($id)
+ {
+ // Normalize the incoming $id to make sure we have a uri
+ $uri = self::normalize($id);
+
+ foreach ($this->methods as $class) {
+ try {
+ $xrd = new XML_XRD();
+
+ common_debug("LRDD discovery method for '$uri': {$class}");
+ $lrdd = new $class;
+ $links = call_user_func(array($lrdd, 'discover'), $uri);
+ $link = Discovery::getService($links, Discovery::LRDD_REL);
+
+ // Load the LRDD XRD
+ if (!empty($link->template)) {
+ $xrd_uri = Discovery::applyTemplate($link->template, $uri);
+ } elseif (!empty($link->href)) {
+ $xrd_uri = $link->href;
+ } else {
+ throw new Exception('No resource descriptor URI in link.');
+ }
+
+ $client = new HTTPClient();
+ $headers = array();
+ if (!is_null($link->type)) {
+ $headers[] = "Accept: {$link->type}";
+ }
+
+ $response = $client->get($xrd_uri, $headers);
+ if ($response->getStatus() != 200) {
+ throw new Exception('Unexpected HTTP status code.');
+ }
+
+ $xrd->loadString($response->getBody());
+ return $xrd;
+ } catch (Exception $e) {
+ continue;
+ }
+ }
+
+ // TRANS: Exception. %s is an ID.
+ throw new Exception(sprintf(_('Unable to find services for %s.'), $id));
+ }
+
+ /**
+ * Given an array of links, returns the matching service
+ *
+ * @param array $links Links to check (as instances of XML_XRD_Element_Link)
+ * @param string $service Service to find
+ *
+ * @return array $link assoc array representing the link
+ */
+ public static function getService(array $links, $service)
+ {
+ foreach ($links as $link) {
+ if ($link->rel === $service) {
+ return $link;
+ }
+ common_debug('LINK: rel '.$link->rel.' !== '.$service);
+ }
+
+ throw new Exception('No service link found');
+ }
+
+ /**
+ * Given a "user id" make sure it's normalized to an acct: uri
+ *
+ * @param string $user_id User ID to normalize
+ *
+ * @return string normalized acct: URI
+ */
+ public static function normalize($uri)
+ {
+ if (is_null($uri) || $uri==='') {
+ throw new Exception(_('No resource given.'));
+ }
+
+ $parts = parse_url($uri);
+ // If we don't have a scheme, but the path implies user@host,
+ // though this is far from a perfect matching procedure...
+ if (!isset($parts['scheme']) && isset($parts['path'])
+ && preg_match('/[\w@\w]/u', $parts['path'])) {
+ return 'acct:' . $uri;
+ }
+
+ return $uri;
+ }
+
+ public static function isAcct($uri)
+ {
+ return (mb_strtolower(mb_substr($uri, 0, 5)) == 'acct:');
+ }
+
+ /**
+ * Apply a template using an ID
+ *
+ * Replaces {uri} in template string with the ID given.
+ *
+ * @param string $template Template to match
+ * @param string $uri URI to replace with
+ *
+ * @return string replaced values
+ */
+ public static function applyTemplate($template, $uri)
+ {
+ $template = str_replace('{uri}', urlencode($uri), $template);
+
+ return $template;
+ }
+}
+
+
--- /dev/null
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * Parse HTTP response for interesting Link: headers
+ *
+ * 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/>.
+ *
+ * @category Discovery
+ * @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/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Class to represent Link: headers in an HTTP response
+ *
+ * Since these are a fairly important part of Hammer-stack discovery, they're
+ * reified and implemented here.
+ *
+ * @category Discovery
+ * @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/
+ *
+ * @see Discovery
+ */
+class LinkHeader
+{
+ var $href;
+ var $rel;
+ var $type;
+
+ /**
+ * Initialize from a string
+ *
+ * @param string $str Link: header value
+ *
+ * @return LinkHeader self
+ */
+ 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);
+ }
+ }
+ }
+
+ /**
+ * Given an HTTP response, return the requested Link: header
+ *
+ * @param HTTP_Request2_Response $response response to check
+ * @param string $rel relationship to look for
+ * @param string $type media type to look for
+ *
+ * @return LinkHeader discovered header, or null on failure
+ */
+ 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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Abstract class for LRDD discovery methods
+ *
+ * Objects that extend this class can retrieve an array of
+ * resource descriptor links for the URI. The array consists
+ * of XML_XRD_Element_Link elements.
+ *
+ * @category Discovery
+ * @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/
+ */
+abstract class LRDDMethod
+{
+ protected $xrd = null;
+
+ public function __construct() {
+ $this->xrd = new XML_XRD();
+ }
+
+ /**
+ * Discover interesting info about the URI
+ *
+ * @param string $uri URI to inquire about
+ *
+ * @return array of XML_XRD_Element_Link elements to discovered resource descriptors
+ */
+ abstract public function discover($uri);
+
+ protected function fetchUrl($url, $method=HTTPClient::METHOD_GET)
+ {
+ $client = new HTTPClient();
+
+ // GAAHHH, this method sucks! How about we make a better HTTPClient interface?
+ switch ($method) {
+ case HTTPClient::METHOD_GET:
+ $response = $client->get($url);
+ break;
+ case HTTPClient::METHOD_HEAD:
+ $response = $client->head($url);
+ break;
+ default:
+ throw new Exception('Bad HTTP method.');
+ }
+
+ if ($response->getStatus() != 200) {
+ throw new Exception('Unexpected HTTP status code.');
+ }
+
+ return $response;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implementation of discovery using host-meta file
+ *
+ * Discovers resource descriptor file for a user by going to the
+ * organization's host-meta file and trying to find a template for LRDD.
+ *
+ * @category Discovery
+ * @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/
+ */
+class LRDDMethod_HostMeta extends LRDDMethod
+{
+ /**
+ * For RFC6415 and HTTP URIs, fetch the host-meta file
+ * and look for LRDD templates
+ */
+ public function discover($uri)
+ {
+ // This is allowed for RFC6415 but not the 'WebFinger' RFC7033.
+ $try_schemes = array('https', 'http');
+
+ $scheme = mb_strtolower(parse_url($uri, PHP_URL_SCHEME));
+ switch ($scheme) {
+ case 'acct':
+ if (!Discovery::isAcct($uri)) {
+ throw new Exception('Bad resource URI: '.$uri);
+ }
+ // We can't use parse_url data for this, since the 'host'
+ // entry is only set if the scheme has '://' after it.
+ list($user, $domain) = explode('@', parse_url($uri, PHP_URL_PATH));
+ break;
+ case 'http':
+ case 'https':
+ $domain = mb_strtolower(parse_url($uri, PHP_URL_HOST));
+ $try_schemes = array($scheme);
+ break;
+ default:
+ throw new Exception('Unable to discover resource descriptor endpoint.');
+ }
+
+ foreach ($try_schemes as $scheme) {
+ $url = $scheme . '://' . $domain . '/.well-known/host-meta';
+
+ try {
+ $response = self::fetchUrl($url);
+ $this->xrd->loadString($response->getBody());
+ } catch (Exception $e) {
+ common_debug('LRDD could not load resource descriptor: '.$url.' ('.$e->getMessage().')');
+ continue;
+ }
+ return $this->xrd->links;
+ }
+
+ throw new Exception('Unable to retrieve resource descriptor links.');
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implementation of discovery using HTTP Link header
+ *
+ * Discovers XRD file for a user by fetching the URL and reading any
+ * Link: headers in the HTTP response.
+ *
+ * @category Discovery
+ * @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/
+ */
+class LRDDMethod_LinkHeader extends LRDDMethod
+{
+ /**
+ * For HTTP IDs fetch the URL and look for Link headers.
+ *
+ * @todo fail out of WebFinger URIs faster
+ */
+ public function discover($uri)
+ {
+ $response = self::fetchUrl($uri, HTTPClient::METHOD_HEAD);
+
+ $link_header = $response->getHeader('Link');
+ if (empty($link_header)) {
+ throw new Exception('No Link header found');
+ }
+ common_debug('LRDD LinkHeader found: '.var_export($link_header,true));
+
+ return self::parseHeader($link_header);
+ }
+
+ /**
+ * Given a string or array of headers, returns JRD-like assoc array
+ *
+ * @param string|array $header string or array of strings for headers
+ *
+ * @return array of associative arrays in JRD-like array format
+ */
+ protected static function parseHeader($header)
+ {
+ $lh = new LinkHeader($header);
+
+ $link = new XML_XRD_Element_Link($lh->rel, $lh->href, $lh->type);
+
+ return array($link);
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implementation of discovery using HTML <link> element
+ *
+ * Discovers XRD file for a user by fetching the URL and reading any
+ * <link> elements in the HTML response.
+ *
+ * @category Discovery
+ * @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/
+ */
+class LRDDMethod_LinkHTML extends LRDDMethod
+{
+ /**
+ * For HTTP IDs, fetch the URL and look for <link> elements
+ * in the HTML response.
+ *
+ * @todo fail out of WebFinger URIs faster
+ */
+ public function discover($uri)
+ {
+ $response = self::fetchUrl($uri);
+
+ return self::parse($response->getBody());
+ }
+
+ /**
+ * Parse HTML and return <link> elements
+ *
+ * Given an HTML string, scans the string for <link> elements
+ *
+ * @param string $html HTML to scan
+ *
+ * @return array array of associative arrays in JRD-ish array format
+ */
+ 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[] = new XML_XRD_Element_Link($link_rel, $link_uri, $link_type);
+ }
+
+ return $links;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Implementation of WebFinger resource discovery (RFC7033)
+ *
+ * @category Discovery
+ * @package GNUSocial
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2013 Free Software Foundation, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+class LRDDMethod_WebFinger extends LRDDMethod
+{
+ /**
+ * Simply returns the WebFinger URL over HTTPS at the uri's domain:
+ * https://{domain}/.well-known/webfinger?resource={uri}
+ */
+ public function discover($uri)
+ {
+ if (!Discovery::isAcct($uri)) {
+ throw new Exception('Bad resource URI: '.$uri);
+ }
+ list($user, $domain) = explode('@', parse_url($uri, PHP_URL_PATH));
+ if (!filter_var($domain, FILTER_VALIDATE_IP)
+ && !filter_var(gethostbyname($domain), FILTER_VALIDATE_IP)) {
+ throw new Exception('Bad resource host.');
+ }
+
+ $link = new XML_XRD_Element_Link(
+ Discovery::LRDD_REL,
+ 'https://' . $domain . '/.well-known/webfinger?resource={uri}',
+ Discovery::JRD_MIMETYPE,
+ true); //isTemplate
+
+ return array($link);
+ }
+}
/**
* OMB plugin main class
*
+ * Depends on: WebFinger plugin
+ *
* @category Integration
* @package StatusNet
* @author Zach Copley <zach@status.net>
*/
/**
+ * OStatusPlugin implementation for GNU Social
+ *
+ * Depends on: WebFinger plugin
+ *
* @package OStatusPlugin
* @maintainer Brion Vibber <brion@status.net>
*/
-if (!defined('STATUSNET')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/');
function onRouterInitialized($m)
{
// Discovery actions
- $m->connect('main/ownerxrd',
- array('action' => 'ownerxrd'));
$m->connect('main/ostatustag',
array('action' => 'ostatustag'));
$m->connect('main/ostatustag?nickname=:nickname',
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('userxrd');
- $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.
return true;
}
- function onEndXrdActionLinks(&$xrd, $user)
+ function onEndXrdActionLinks(XML_XRD $xrd, Profile $target)
{
- $xrd->links[] = array('rel' => Discovery::UPDATESFROM,
- 'href' => common_local_url('ApiTimelineUser',
- array('id' => $user->id,
- 'format' => 'atom')),
- 'type' => 'application/atom+xml');
+ $xrd->links[] = new XML_XRD_Element_Link(Discovery::UPDATESFROM,
+ common_local_url('ApiTimelineUser',
+ array('id' => $target->id, 'format' => 'atom')),
+ 'application/atom+xml');
// Salmon
$salmon_url = common_local_url('usersalmon',
- array('id' => $user->id));
+ array('id' => $target->id));
- $xrd->links[] = array('rel' => Salmon::REL_SALMON,
- 'href' => $salmon_url);
+ $xrd->links[] = new XML_XRD_Element_Link(Salmon::REL_SALMON, $salmon_url);
// XXX : Deprecated - to be removed.
- $xrd->links[] = array('rel' => Salmon::NS_REPLIES,
- 'href' => $salmon_url);
-
- $xrd->links[] = array('rel' => Salmon::NS_MENTIONS,
- 'href' => $salmon_url);
+ $xrd->links[] = new XML_XRD_Element_Link(Salmon::NS_REPLIES, $salmon_url);
+ $xrd->links[] = new XML_XRD_Element_Link(Salmon::NS_MENTIONS, $salmon_url);
// Get this user's keypair
- $magickey = Magicsig::getKV('user_id', $user->id);
- if (!$magickey) {
+ $magickey = Magicsig::getKV('user_id', $target->id);
+ if (!($magickey instanceof Magicsig)) {
// No keypair yet, let's generate one.
$magickey = new Magicsig();
- $magickey->generate($user->id);
+ $magickey->generate($target->id);
}
- $xrd->links[] = array('rel' => Magicsig::PUBLICKEYREL,
- 'href' => 'data:application/magic-public-key,'. $magickey->toString(false));
+ $xrd->links[] = new XML_XRD_Element_Link(Magicsig::PUBLICKEYREL,
+ '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}';
- $xrd->links[] = array('rel' => 'http://ostatus.org/schema/1.0/subscribe',
- 'template' => $url );
+ $xrd->links[] = new XML_XRD_Element_Link('http://ostatus.org/schema/1.0/subscribe',
+ common_local_url('ostatussub') . '?profile={uri}',
+ null, // type not set
+ true); // isTemplate
return true;
}
$target_profile = $this->targetProfile();
$disco = new Discovery;
- $result = $disco->lookup($acct);
- if (!$result) {
- // TRANS: Client error.
- $this->clientError(_m('Could not look up OStatus account profile.'));
- }
-
- foreach ($result->links as $link) {
- if ($link['rel'] == 'http://ostatus.org/schema/1.0/subscribe') {
- // We found a URL - let's redirect!
- $url = Discovery::applyTemplate($link['template'], $target_profile);
- common_log(LOG_INFO, "Sending remote subscriber $acct to $url");
- common_redirect($url, 303);
- }
-
+ $xrd = $disco->lookup($acct);
+
+ $link = $xrd->get('http://ostatus.org/schema/1.0/subscribe');
+ if (!is_null($link)) {
+ // We found a URL - let's redirect!
+ $url = Discovery::applyTemplate($link['template'], $target_profile);
+ common_log(LOG_INFO, "Sending remote subscriber $acct to $url");
+ common_redirect($url, 303);
}
// TRANS: Client error.
$this->clientError(_m('Could not confirm remote profile address.'));
$target_profile = $this->targetProfile();
$disco = new Discovery;
- $result = $disco->lookup($acct);
- if (!$result) {
- // TRANS: Client error displayed when remote profile could not be looked up.
- $this->clientError(_m('Could not look up OStatus account profile.'));
- }
-
- foreach ($result->links as $link) {
- if ($link['rel'] == 'http://ostatus.org/schema/1.0/tag') {
- // We found a URL - let's redirect!
- $url = Discovery::applyTemplate($link['template'], $target_profile);
- common_log(LOG_INFO, "Sending remote subscriber $acct to $url");
- common_redirect($url, 303);
- }
-
+ $xrd = $disco->lookup($acct);
+
+ $link = $xrd->get('http://ostatus.org/schema/1.0/tag');
+ if (!is_null($link)) {
+ // We found a URL - let's redirect!
+ $url = Discovery::applyTemplate($link->template, $target_profile);
+ common_log(LOG_INFO, "Sending remote subscriber $acct to $url");
+ common_redirect($url, 303);
}
// TRANS: Client error displayed when remote profile address could not be confirmed.
$this->clientError(_m('Could not confirm remote profile address.'));
+++ /dev/null
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, Inc.
- *
- * 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 OStatusPlugin
- * @maintainer James Walker <james@status.net>
- */
-
-if (!defined('STATUSNET')) {
- exit(1);
-}
-
-class OwnerxrdAction extends XrdAction
-{
-
- public $uri;
-
- function prepare($args)
- {
- $this->user = User::siteOwner();
-
- if (!$this->user) {
- // TRANS: Client error displayed when referring to a non-existing user.
- $this->clientError(_m('No such user.'), 404);
- return false;
- }
-
- $nick = common_canonical_nickname($this->user->nickname);
- $acct = 'acct:' . $nick . '@' . common_config('site', 'server');
-
- $this->xrd = new XRD();
-
- // Check to see if a $config['webfinger']['owner'] has been set
- if ($owner = common_config('webfinger', 'owner')) {
- $this->xrd->subject = Discovery::normalize($owner);
- $this->xrd->alias[] = $acct;
- } else {
- $this->xrd->subject = $acct;
- }
-
- return true;
- }
-}
+++ /dev/null
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, Inc.
- *
- * 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 OStatusPlugin
- * @maintainer James Walker <james@status.net>
- */
-
-if (!defined('STATUSNET')) {
- exit(1);
-}
-
-class XrdAction extends Action
-{
- public $uri;
-
- public $user;
-
- public $xrd;
-
- function handle()
- {
- $nick = $this->user->nickname;
- $profile = $this->user->getProfile();
-
- if (empty($this->xrd)) {
- $xrd = new XRD();
- } else {
- $xrd = $this->xrd;
- }
-
- if (empty($xrd->subject)) {
- $xrd->subject = Discovery::normalize($this->uri);
- }
-
- // Possible aliases for the user
-
- $uris = array($this->user->uri, $profile->profileurl);
-
- // FIXME: Webfinger generation code should live somewhere on its own
-
- $path = common_config('site', 'path');
-
- if (empty($path)) {
- $uris[] = sprintf('acct:%s@%s', $nick, common_config('site', 'server'));
- }
-
- foreach ($uris as $uri) {
- if ($uri != $xrd->subject) {
- $xrd->alias[] = $uri;
- }
- }
-
- $xrd->links[] = array('rel' => Discovery::PROFILEPAGE,
- 'type' => 'text/html',
- 'href' => $profile->profileurl);
-
- $xrd->links[] = array('rel' => Discovery::UPDATESFROM,
- 'href' => common_local_url('ApiTimelineUser',
- array('id' => $this->user->id,
- 'format' => 'atom')),
- 'type' => 'application/atom+xml');
-
- // XFN
- $xrd->links[] = array('rel' => 'http://gmpg.org/xfn/11',
- 'type' => 'text/html',
- 'href' => $profile->profileurl);
- // FOAF
- $xrd->links[] = array('rel' => 'describedby',
- 'type' => 'application/rdf+xml',
- 'href' => common_local_url('foaf',
- array('nickname' => $nick)));
-
- // Salmon
- $salmon_url = common_local_url('usersalmon',
- array('id' => $this->user->id));
-
- $xrd->links[] = array('rel' => Salmon::REL_SALMON,
- 'href' => $salmon_url);
- // XXX : Deprecated - to be removed.
- $xrd->links[] = array('rel' => Salmon::NS_REPLIES,
- 'href' => $salmon_url);
-
- $xrd->links[] = array('rel' => Salmon::NS_MENTIONS,
- 'href' => $salmon_url);
-
- // Get this user's keypair
- $magickey = Magicsig::getKV('user_id', $this->user->id);
- if (!$magickey) {
- // No keypair yet, let's generate one.
- $magickey = new Magicsig();
- $magickey->generate($this->user->id);
- }
-
- $xrd->links[] = array('rel' => Magicsig::PUBLICKEYREL,
- '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}';
- $xrd->links[] = array('rel' => 'http://ostatus.org/schema/1.0/subscribe',
- 'template' => $url );
-
- $url = common_local_url('tagprofile') . '?uri={uri}';
- $xrd->links[] = array('rel' => 'http://ostatus.org/schema/1.0/tag',
- 'template' => $url );
-
- header('Content-type: application/xrd+xml');
- print $xrd->toXML();
- }
-}
// Check if they've got an LRDD header
- $lrdd = LinkHeader::getLink($response, 'lrdd', 'application/xrd+xml');
-
- if (!empty($lrdd)) {
-
- $xrd = Discovery::fetchXrd($lrdd);
+ $lrdd = LinkHeader::getLink($response, 'lrdd');
+ try {
+ $xrd = new XML_XRD();
+ $xrd->loadFile($lrdd);
$xrdHints = DiscoveryHints::fromXRD($xrd);
-
$hints = array_merge($hints, $xrdHints);
+ } catch (Exception $e) {
+ // No hints available from XRD
}
// If discovery found a feedurl (probably from LRDD), use it.
*/
class DiscoveryHints {
- static function fromXRD($xrd)
+ static function fromXRD(XML_XRD $xrd)
{
$hints = array();
- foreach ($xrd->links as $link) {
- switch ($link['rel']) {
- case Discovery::PROFILEPAGE:
- $hints['profileurl'] = $link['href'];
+ foreach ($xrd->getAll() as $link) {
+ switch ($link->rel) {
+ case WebFinger::PROFILEPAGE:
+ $hints['profileurl'] = $link->href;
break;
case Salmon::NS_MENTIONS:
case Salmon::NS_REPLIES:
- $hints['salmon'] = $link['href'];
+ $hints['salmon'] = $link->href;
break;
case Discovery::UPDATESFROM:
- if (empty($link['type']) || $link['type'] == 'application/atom+xml') {
- $hints['feedurl'] = $link['href'];
+ if (empty($link->type) || $link->type == 'application/atom+xml') {
+ $hints['feedurl'] = $link->href;
}
break;
case Discovery::HCARD:
- $hints['hcardurl'] = $link['href'];
+ $hints['hcardurl'] = $link->href;
break;
default:
break;
} catch (Exception $e) {
return false;
}
- if ($xrd->links) {
- if ($link = Discovery::getService($xrd->links, Magicsig::PUBLICKEYREL)) {
- $keypair = false;
- $parts = explode(',', $link['href']);
+ $link = $xrd->get(Magicsig::PUBLICKEYREL);
+ if (!is_null($link)) {
+ $keypair = false;
+ $parts = explode(',', $link['href']);
+ if (count($parts) == 2) {
+ $keypair = $parts[1];
+ } else {
+ // Backwards compatibility check for separator bug in 0.9.0
+ $parts = explode(';', $link['href']);
if (count($parts) == 2) {
$keypair = $parts[1];
- } else {
- // Backwards compatibility check for separator bug in 0.9.0
- $parts = explode(';', $link['href']);
- if (count($parts) == 2) {
- $keypair = $parts[1];
- }
}
+ }
- if ($keypair) {
- return $keypair;
- }
+ if ($keypair) {
+ return $keypair;
}
}
// TRANS: Exception.
// Check if they've got an LRDD header
$lrdd = LinkHeader::getLink($response, 'lrdd', 'application/xrd+xml');
-
- if (!empty($lrdd)) {
-
- $xrd = Discovery::fetchXrd($lrdd);
+ try {
+ $xrd = new XML_XRD();
+ $xrd->loadFile($lrdd);
$xrdHints = DiscoveryHints::fromXRD($xrd);
-
$hints = array_merge($hints, $xrdHints);
+ } catch (Exception $e) {
+ // No hints available from XRD
}
// If discovery found a feedurl (probably from LRDD), use it.
* This class enables consumer support for OpenID, the distributed authentication
* and identity system.
*
+ * Depends on: WebFinger plugin for HostMeta-lookup (user@host format)
+ *
* @category Plugin
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
}
/**
- * We include a <meta> element linking to the userxrds page, for OpenID
- * client-side authentication.
+ * We include a <meta> element linking to the webfinger resource page,
+ * for OpenID client-side authentication.
*
* @param Action $action Action being shown
*
* Webfinger identity to services that support it. See
* http://webfinger.org/login for an example.
*
- * @param XRD &$xrd Currently-displaying XRD object
- * @param User $user The user that it's for
+ * @param XML_XRD $xrd Currently-displaying resource descriptor
+ * @param Profile $target The profile that it's for
*
* @return boolean hook value (always true)
*/
- function onEndXrdActionLinks(&$xrd, $user)
+ function onEndXrdActionLinks(XML_XRD $xrd, Profile $target)
{
- $profile = $user->getProfile();
-
- if (!empty($profile)) {
- $xrd->links[] = array('rel' => 'http://specs.openid.net/auth/2.0/provider',
- 'href' => $profile->profileurl);
- }
+ $xrd->links[] = new XML_XRD_Element_Link(
+ 'http://specs.openid.net/auth/2.0/provider',
+ $target->profileurl);
return true;
}
--- /dev/null
+StartHostMetaLinks: Start /.well-known/host-meta links
+- &links: array containing the links elements to be written
+
+EndHostMetaLinks: End /.well-known/host-meta links
+- &links: array containing the links elements to be written
+
+StartWebFingerReconstruction:
+- $profile: Profile object for which we want a WebFinger ID
+- &$acct: String reference where reconstructed ID is stored
+
+EndWebFingerReconstruction:
+- $profile: Profile object for which we want a WebFinger ID
+- &$acct: String reference where reconstructed ID is stored
+
+StartXrdActionAliases: About to set aliases for the XRD for a user
+- $xrd: XML_XRD object being shown
+- $target: Profile being shown
+
+EndXrdActionAliases: Done with aliases for the XRD for a user
+- $xrd: XML_XRD object being shown
+- $target: Profile being shown
+
+StartXrdActionLinks: About to set links for the XRD for a profile
+- $xrd: XML_XRD object being shown
+- $target: Profile being shown
+
+EndXrdActionLinks: Done with links for the XRD for a profile
+- $xrd: XML_XRD object being shown
+- $target: Profile being shown
--- /dev/null
+<?php
+/*
+ * GNU Social - a federating social network
+ * Copyright (C) 2013, Free Software Foundation, Inc.
+ *
+ * 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/>.
+ */
+
+/**
+ * Implements WebFinger for GNU Social, as well as support for the
+ * '.well-known/host-meta' resource.
+ *
+ * Depends on: LRDD plugin
+ *
+ * @package GNUSocial
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class WebFingerPlugin extends Plugin
+{
+ public function onRouterInitialized($m)
+ {
+ $m->connect('.well-known/host-meta', array('action' => 'hostmeta'));
+ $m->connect('.well-known/host-meta.:format',
+ array('action' => 'hostmeta',
+ 'format' => '(xml|json)'));
+ // the resource GET parameter can be anywhere, so don't mention it here
+ $m->connect('.well-known/webfinger', array('action' => 'webfinger'));
+ $m->connect('.well-known/webfinger.:format',
+ array('action' => 'webfinger',
+ 'format' => '(xml|json)'));
+ $m->connect('main/ownerxrd', array('action' => 'ownerxrd'));
+ return true;
+ }
+
+ public function onLoginAction($action, &$login)
+ {
+ switch ($action) {
+ case 'hostmeta':
+ case 'webfinger':
+ $login = true;
+ return false;
+ }
+
+ return true;
+ }
+
+ public function onStartHostMetaLinks(array &$links)
+ {
+ foreach (Discovery::supportedMimeTypes() as $type) {
+ $links[] = new XML_XRD_Element_Link(Discovery::LRDD_REL,
+ common_local_url('webfinger') . '?resource={uri}',
+ $type,
+ true); // isTemplate
+ }
+ }
+
+ /**
+ * Add a link header for LRDD Discovery
+ */
+ public function onStartShowHTML($action)
+ {
+ if ($action instanceof ShowstreamAction) {
+ $acct = 'acct:'. $action->profile->nickname .'@'. common_config('site', 'server');
+ $url = common_local_url('webfinger') . '?resource='.$acct;
+
+ foreach (array(Discovery::JRD_MIMETYPE, Discovery::XRD_MIMETYPE) as $type) {
+ header('Link: <'.$url.'>; rel="'. Discovery::LRDD_REL.'"; type="'.$type.'"');
+ }
+ }
+ }
+
+ public function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'WebFinger',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Mikael Nordfeldth',
+ 'homepage' => 'http://www.gnu.org/software/social/',
+ // TRANS: Plugin description.
+ 'rawdescription' => _m('Adds WebFinger lookup to GNU Social'));
+
+ return true;
+ }
+}
--- /dev/null
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * 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/>.
+ */
+
+/**
+ * @category Action
+ * @package StatusNet
+ * @author James Walker <james@status.net>
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+// @todo XXX: Add documentation.
+class HostMetaAction extends XrdAction
+{
+ protected $defaultformat = 'xml';
+
+ protected function setXRD()
+ {
+ if(Event::handle('StartHostMetaLinks', array(&$this->xrd->links))) {
+ Event::handle('EndHostMetaLinks', array(&$this->xrd->links));
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * 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 WebFingerPlugin
+ * @author James Walker <james@status.net>
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class OwnerxrdAction extends WebfingerAction
+{
+ protected $defaultformat = 'xml';
+
+ protected function prepare(array $args=array())
+ {
+ $user = User::siteOwner();
+
+ $nick = common_canonical_nickname($user->nickname);
+ $args['resource'] = 'acct:' . $nick . '@' . common_config('site', 'server');
+
+ // We have now set $args['resource'] to the configured value, since
+ // only this local site configuration knows who the owner is!
+ parent::prepare($args);
+
+ return true;
+ }
+
+ protected function setXRD()
+ {
+ parent::setXRD();
+
+ // Check to see if a $config['webfinger']['owner'] has been set
+ // and then make sure 'subject' is set to that primary identity.
+ if ($owner = common_config('webfinger', 'owner')) {
+ $this->xrd->aliases[] = $this->xrd->subject;
+ $this->xrd->subject = Discovery::normalize($owner);
+ } else {
+ $this->xrd->subject = $this->resource;
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * 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/>.
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+/**
+ * @package WebFingerPlugin
+ * @author James Walker <james@status.net>
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ */
+class WebfingerAction extends XrdAction
+{
+ protected function prepare(array $args=array())
+ {
+ parent::prepare($args);
+
+ // throws exception if resource is empty
+ $this->resource = Discovery::normalize($this->trimmed('resource'));
+
+ if (Discovery::isAcct($this->resource)) {
+ $parts = explode('@', substr(urldecode($this->resource), 5));
+ if (count($parts) == 2) {
+ list($nick, $domain) = $parts;
+ if ($domain === common_config('site', 'server')) {
+ $nick = common_canonical_nickname($nick);
+ $user = User::getKV('nickname', $nick);
+ if (!($user instanceof User)) {
+ throw new NoSuchUserException(array('nickname'=>$nick));
+ }
+ $this->target = $user->getProfile();
+ } else {
+ throw new Exception(_('Remote profiles not supported via WebFinger yet.'));
+ }
+ }
+ } else {
+ $user = User::getKV('uri', $this->resource);
+ if ($user instanceof User) {
+ $this->target = $user->getProfile();
+ } else {
+ // try and get it by profile url
+ $this->target = Profile::getKV('profileurl', $this->resource);
+ }
+ }
+
+ if (!($this->target instanceof Profile)) {
+ // TRANS: Client error displayed when user not found for an action.
+ $this->clientError(_('No such user: ') . var_export($this->resource,true), 404);
+ }
+
+ return true;
+ }
+
+ protected function setXRD()
+ {
+ if (empty($this->target)) {
+ throw new Exception(_('Target not set for resource descriptor'));
+ }
+
+ // $this->target set in a _child_ class prepare()
+ $nick = $this->target->nickname;
+
+ $this->xrd->subject = $this->resource;
+
+ if (Event::handle('StartXrdActionAliases', array($this->xrd, $this->target))) {
+ $uris = WebFinger::getIdentities($this->target);
+ foreach ($uris as $uri) {
+ if ($uri != $this->xrd->subject && !in_array($uri, $this->xrd->aliases)) {
+ $this->xrd->aliases[] = $uri;
+ }
+ }
+ Event::handle('EndXrdActionAliases', array($this->xrd, $this->target));
+ }
+
+ if (Event::handle('StartXrdActionLinks', array($this->xrd, $this->target))) {
+
+ $this->xrd->links[] = new XML_XRD_Element_Link(WebFinger::PROFILEPAGE,
+ $this->target->getUrl(), 'text/html');
+
+ // XFN
+ $this->xrd->links[] = new XML_XRD_Element_Link('http://gmpg.org/xfn/11',
+ $this->target->getUrl(), 'text/html');
+ // FOAF
+ $this->xrd->links[] = new XML_XRD_Element_Link('describedby',
+ common_local_url('foaf', array('nickname' => $nick)),
+ 'application/rdf+xml');
+
+ $link = new XML_XRD_Element_Link('http://apinamespace.org/atom',
+ common_local_url('ApiAtomService', array('id' => $nick)),
+ 'application/atomsvc+xml');
+// XML_XRD must implement changing properties first $link['http://apinamespace.org/atom/username'] = $nick;
+ $this->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'] = $nick;
+ $this->xrd->links[] = clone $link;
+
+ Event::handle('EndXrdActionLinks', array($this->xrd, $this->target));
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * WebFinger functions
+ *
+ * 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 GNUSocial
+ * @author Mikael Nordfeldth
+ * @copyright 2013 Free Software Foundation, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class WebFinger
+{
+ const PROFILEPAGE = 'http://webfinger.net/rel/profile-page';
+
+ /*
+ * Reconstructs a WebFinger ID from data we know about the profile.
+ *
+ * @param Profile $profile The profile we want a WebFinger ID for
+ *
+ * @return string $acct acct:user@example.com URI
+ */
+ public static function reconstruct(Profile $profile)
+ {
+ $acct = null;
+
+ if (Event::handle('StartWebFingerReconstruction', array($profile, &$acct))) {
+ // TODO: getUri may not always give us the correct host on remote users?
+ $host = parse_url($profile->getUri(), PHP_URL_HOST);
+ if (empty($profile->nickname) || empty($host)) {
+ throw new WebFingerReconstructionException($profile);
+ }
+ $acct = sprintf('acct:%s@%s', $profile->nickname, $host);
+
+ Event::handle('EndWebFingerReconstruction', array($profile, &$acct));
+ }
+
+ return $acct;
+ }
+
+ /*
+ * Gets all URI aliases for a Profile
+ *
+ * @param Profile $profile The profile we want aliases for
+ *
+ * @return array $aliases All the Profile's alternative URLs
+ */
+ public static function getAliases(Profile $profile)
+ {
+ $aliases = array();
+ $aliases[] = $profile->getUri();
+ try {
+ $aliases[] = $profile->getUrl();
+ } catch (InvalidUrlException $e) {
+ common_debug('Profile id='.$profile->id.' has invalid profileurl: ' .
+ var_export($profile->profileurl, true));
+ }
+ return $aliases;
+ }
+
+ /*
+ * Gets all identities for a Profile, includes WebFinger acct: if
+ * available, as well as alias URLs.
+ *
+ * @param Profile $profile The profile we want aliases for
+ *
+ * @return array $uris WebFinger acct: URI and alias URLs
+ */
+ public static function getIdentities(Profile $profile)
+ {
+ $uris = array();
+ try {
+ $uris[] = self::reconstruct($profile);
+ } catch (WebFingerReconstructionException $e) {
+ common_debug('WebFinger reconstruction for Profile failed, ' .
+ ' (id='.$profile->id.')');
+ }
+ $uris = array_merge($uris, self::getAliases($profile));
+
+ return $uris;
+ }
+}
--- /dev/null
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for an exception when a WebFinger acct: URI can not be constructed
+ * using the data we have in a Profile.
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category Exception
+ * @package StatusNet
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2013 Free Software Foundation, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://status.net/
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+/**
+ * Class for an exception when a WebFinger acct: URI can not be constructed
+ * using the data we have in a Profile.
+ *
+ * @category Exception
+ * @package StatusNet
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://status.net/
+ */
+
+class WebFingerReconstructionException extends ServerException
+{
+ public $target = null;
+
+ public function __construct(Profile $target)
+ {
+ $this->target = $target;
+
+ // We could log an entry here with the search parameters
+ parent::__construct(_('WebFinger URI generation failed.'));
+ }
+}
--- /dev/null
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * 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 WebFingerPlugin
+ * @author James Walker <james@status.net>
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+abstract class XrdAction extends Action
+{
+ // json or xml for now, this may still be overriden because of
+ // our back-compatibility with StatusNet <=1.1.1
+ protected $defaultformat = null;
+
+ protected $resource = null;
+ protected $target = null;
+ protected $xrd = null;
+
+ public function isReadOnly($args)
+ {
+ return true;
+ }
+
+ /*
+ * Configures $this->xrd which will later be printed. Must be
+ * implemented by child classes.
+ */
+ abstract protected function setXRD();
+
+ protected function prepare(array $args=array())
+ {
+ if (!isset($args['format'])) {
+ $args['format'] = $this->defaultformat;
+ }
+
+ parent::prepare($args);
+
+ $this->xrd = new XML_XRD();
+
+ return true;
+ }
+
+ protected function handle()
+ {
+ parent::handle();
+
+ $this->setXRD();
+
+ if (common_config('discovery', 'cors')) {
+ header('Access-Control-Allow-Origin: *');
+ }
+
+ $this->showPage();
+ }
+
+ public function mimeType()
+ {
+ try {
+ return $this->checkAccept();
+ } catch (Exception $e) {
+ $supported = Discovery::supportedMimeTypes();
+ $docformat = $this->arg('format');
+
+ if (!empty($docformat) && isset($supported[$docformat])) {
+ return $supported[$docformat];
+ }
+ }
+
+ /*
+ * "A WebFinger resource MUST return a JRD as the representation
+ * for the resource if the client requests no other supported
+ * format explicitly via the HTTP "Accept" header. [...]
+ * The WebFinger resource MUST silently ignore any requested
+ * representations that it does not understand and support."
+ * -- RFC 7033 (WebFinger)
+ * http://tools.ietf.org/html/rfc7033
+ */
+ return Discovery::JRD_MIMETYPE;
+ }
+
+ public function showPage()
+ {
+ $mimeType = $this->mimeType();
+ header("Content-type: {$mimeType}");
+
+ switch ($mimeType) {
+ case Discovery::XRD_MIMETYPE:
+ print $this->xrd->toXML();
+ break;
+ case Discovery::JRD_MIMETYPE:
+ case Discovery::JRD_MIMETYPE_OLD:
+ print $this->xrd->to('json');
+ break;
+ default:
+ throw new Exception(_('No supported MIME type in Accept header.'));
+ }
+ }
+
+ protected function checkAccept()
+ {
+ $type = null;
+ $httpaccept = isset($_SERVER['HTTP_ACCEPT'])
+ ? $_SERVER['HTTP_ACCEPT'] : null;
+ $useragent = isset($_SERVER['HTTP_USER_AGENT'])
+ ? $_SERVER['HTTP_USER_AGENT'] : null;
+
+ if ($httpaccept !== null && $httpaccept != '*/*') {
+ $can_serve = implode(',', Discovery::supportedMimeTypes());
+ $type = common_negotiate_type(common_accept_to_prefs($httpaccept),
+ common_accept_to_prefs($can_serve));
+ } else {
+ /*
+ * HACK: for StatusNet to work against us, we must always serve an
+ * XRD to at least versions <1.1.1 (at time of writing) since they
+ * don't send Accept headers (in their 'Discovery::fetchXrd' calls)
+ */
+ $matches = array();
+ preg_match('/(StatusNet)\/(\d+\.\d+(\.\d+)?)/', $useragent, $browser);
+ if (count($browser)>2 && $browser[1] === 'StatusNet'
+ && version_compare($browser[2], '1.1.1') < 1) {
+ return Discovery::XRD_MIMETYPE;
+ }
+ }
+
+ if (empty($type)) {
+ throw new Exception(_('No specified MIME type in Accept header.'));
+ }
+
+ return $type;
+ }
+}
exit(1);
}
} else if (have_option('o', 'owner')) {
- $user = User::siteOwner();
- if (empty($user)) {
+ try {
+ $user = User::siteOwner();
+ } catch (ServerException $e) {
print "Site has no owner.\n";
exit(1);
}
-<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0" xmlns:hm="http://host-meta.net/xrd/1.0"><hm:Host>example.com</hm:Host><Link rel="lrdd" template="http://example.com/xrd?uri={uri}"><Title>Resource Descriptor</Title></Link></XRD>
\ No newline at end of file
+<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0" xmlns:hm="http://host-meta.net/xrd/1.0"><hm:Host>example.com</hm:Host><Link rel="lrdd" template="http://example.com/.well-known/webfinger?resource={uri}"><Title>WebFinger resource descriptor</Title></Link></XRD>