code:
# minimalist openssl.cnf file for use with phpseclib

HOME			= .
RANDFILE		= $ENV::HOME/.rnd

[ v3_ca ]


code:
Feed icon rendered from http://commons.wikimedia.org/wiki/File:Feed-icon.svg

Originally distributed by the Mozilla Foundation under a MPL/GPL/LGPL tri-license:

http://www.mozilla.org/MPL/boilerplate-1.1/mpl-tri-license-html

repo_name: piratas/tortuga

path: plugins/OStatus/images/README

license: mit


code:
/*
 * StatusNet - a 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  OStatus UI interaction
 * @package   StatusNet
 * @author    Sarven Capadisli <csarven@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 * @note      Everything in here should eventually migrate over to /js/util.js's SN.
 */

SN.Init.OStatusCookie = function () {
    if (SN.U.StatusNetInstance.Get() === null) {
        SN.U.StatusNetInstance.Set({RemoteProfile: null});
    }
};

SN.U.DialogBox = {
    Subscribe: function (a) {
        var f = a.parent().find('.form_settings');
        if (f.length > 0) {
            f.show();
        }
        else {
            $.ajax({
                type: 'GET',
                dataType: 'xml',
                url: a[0].href + ((a[0].href.match(/[\\?]/) === null)?'?':'&') + 'ajax=1',
                beforeSend: function (formData) {
                    a.addClass('processing');
                },
                error: function (xhr, textStatus, errorThrown) {
                    alert(errorThrown || textStatus);
                },
                success: function (data, textStatus, xhr) {
                    if (typeof($('form', data)[0]) != 'undefined') {
                        a.after(document._importNode($('form', data)[0], true));

                        var form = a.parent().find('.form_settings');

                        form
                            .addClass('dialogbox')
                            .append('<button class="close">×</button>');

                        form
                            .find('.submit')
                                .addClass('submit_dialogbox')
                                .removeClass('submit')
                                .bind('click', function () {
                                    form.addClass('processing');
                                });

                        form.find('button.close').click(function (){
                            form.hide();

                            return false;
                        });

                        form.find('#profile').focus();

                        if (form.attr('id') == 'form_ostatus_connect') {
                            SN.Init.OStatusCookie();
                            form.find('#profile').val(SN.U.StatusNetInstance.Get().RemoteProfile);

                            form.find("[type=submit]").bind('click', function () {
                                SN.U.StatusNetInstance.Set({RemoteProfile: form.find('#profile').val()});
                                return true;
                            });
                        }
                    }

                    a.removeClass('processing');
                }
            });
        }
    }
};

SN.Init.Subscribe = function () {
    $(document).on('click',
        '.entity_subscribe .entity_remote_subscribe, .entity_tag .entity_remote_tag',
        function () { SN.U.DialogBox.Subscribe($(this)); return false; });
};

$(document).ready(function () {
    SN.Init.Subscribe();

    $('.form_remote_authorize').bind('submit', function () {
            $(this).addClass(SN.C.S.Processing); return true;
        });
});


code:
<?php
/*
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2010, StatusNet, Inc.
 *
 * Some utilities for generating hint data
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

class DiscoveryHints {
    static function fromXRD(XML_XRD $xrd)
    {
        $hints = array();

        foreach ($xrd->links as $link) {
            switch ($link->rel) {
            case WebFingerResource_Profile::PROFILEPAGE:
                $hints['profileurl'] = $link->href;
                break;
            case Salmon::REL_SALMON:
            case Salmon::NS_MENTIONS:   // XXX: deprecated, remove in the future
            case Salmon::NS_REPLIES:    // XXX: deprecated, remove in the future
                $hints['salmon'] = $link->href;
                break;
            case Discovery::UPDATESFROM:
                if (empty($link->type) || $link->type == 'application/atom+xml') {
                    $hints['feedurl'] = $link->href;
                }
                break;
            case Discovery::HCARD:
            case Discovery::MF2_HCARD:
                $hints['hcard'] = $link->href;
                break;
            default:
                break;
            }
        }

        return $hints;
    }

    static function fromHcardUrl($url)
    {
        $client = new HTTPClient();
        $client->setHeader('Accept', 'text/html,application/xhtml+xml');
        $response = $client->get($url);

        if (!$response->isOk()) {
            return null;
        }

        return self::hcardHints($response->getBody(),
                                $response->getUrl());
    }

    static function hcardHints($body, $url)
    {
        $hcard = self::_hcard($body, $url);

        if (empty($hcard)) {
            return array();
        }

        $hints = array();

        // XXX: don't copy stuff into an array and then copy it again

        if (array_key_exists('nickname', $hcard) && !empty($hcard['nickname'][0])) {
            $hints['nickname'] = $hcard['nickname'][0];
        }

        if (array_key_exists('name', $hcard) && !empty($hcard['name'][0])) {
            $hints['fullname'] = $hcard['name'][0];
        }

        if (array_key_exists('photo', $hcard) && count($hcard['photo'])) {
            $hints['avatar'] = $hcard['photo'][0];
        }

        if (array_key_exists('note', $hcard) && !empty($hcard['note'][0])) {
            $hints['bio'] = $hcard['note'][0];
        }

        if (array_key_exists('adr', $hcard) && !empty($hcard['adr'][0])) {
            $hints['location'] = $hcard['adr'][0]['value'];
        }

        if (array_key_exists('url', $hcard) && !empty($hcard['url'][0])) {
            $hints['homepage'] = $hcard['url'][0];
        }

        return $hints;
    }

    static function _hcard($body, $url)
    {
        $mf2 = new Mf2\Parser($body, $url);
        $mf2 = $mf2->parse();

        if (empty($mf2['items'])) {
            return null;
        }

        $hcards = array();

        foreach ($mf2['items'] as $item) {
            if (!in_array('h-card', $item['type'])) {
                continue;
            }

            // We found a match, return it immediately
            if (isset($item['properties']['url']) && in_array($url, $item['properties']['url'])) {
                return $item['properties'];
            }

            // Let's keep all the hcards for later, to return one of them at least
            $hcards[] = $item['properties'];
        }

        // No match immediately for the url we expected, but there were h-cards found
        if (count($hcards) > 0) {
            return $hcards[0];
        }

        return null;
    }
}


code:
<?php
/*
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2009, 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 FeedSubPlugin
 * @maintainer Brion Vibber <brion@status.net>
 */

if (!defined('STATUSNET')) {
    exit(1);
}

class FeedSubBadURLException extends FeedSubException
{
}

class FeedSubBadResponseException extends FeedSubException
{
}

class FeedSubEmptyException extends FeedSubException
{
}

class FeedSubBadHTMLException extends FeedSubException
{
}

class FeedSubUnrecognizedTypeException extends FeedSubException
{
}

class FeedSubNoFeedException extends FeedSubException
{
}

class FeedSubBadXmlException extends FeedSubException
{
}

class FeedSubNoHubException extends FeedSubException
{
}

/**
 * Given a web page or feed URL, discover the final location of the feed
 * and return its current contents.
 *
 * @example
 *   $feed = new FeedDiscovery();
 *   if ($feed->discoverFromURL($url)) {
 *     print $feed->uri;
 *     print $feed->type;
 *     processFeed($feed->feed); // DOMDocument
 *   }
 */
class FeedDiscovery
{
    public $uri;
    public $type;
    public $feed;
    public $root;

    /** Post-initialize query helper... */
    public function getLink($rel, $type=null)
    {
        // @fixme check for non-Atom links in RSS2 feeds as well
        return self::getAtomLink($rel, $type);
    }

    public function getAtomLink($rel, $type=null)
    {
        return ActivityUtils::getLink($this->root, $rel, $type);
    }

    /**
     * Get the referenced PuSH hub link from an Atom feed.
     *
     * @return mixed string or false
     */
    public function getHubLink()
    {
        return $this->getAtomLink('hub');
    }

    /**
     * @param string $url
     * @param bool $htmlOk pass false here if you don't want to follow web pages.
     * @return string with validated URL
     * @throws FeedSubBadURLException
     * @throws FeedSubBadHtmlException
     * @throws FeedSubNoFeedException
     * @throws FeedSubEmptyException
     * @throws FeedSubUnrecognizedTypeException
     */
    function discoverFromURL($url, $htmlOk=true)
    {
        try {
            $client = new HTTPClient();
            $response = $client->get($url);
        } catch (HTTP_Request2_Exception $e) {
            common_log(LOG_ERR, __METHOD__ . " Failure for $url - " . $e->getMessage());
            throw new FeedSubBadURLException($e->getMessage());
        }

        if ($htmlOk) {
            $type = $response->getHeader('Content-Type');
            $isHtml = preg_match('!^(text/html|application/xhtml\+xml)!i', $type);
            if ($isHtml) {
                $target = $this->discoverFromHTML($response->getUrl(), $response->getBody());
                if (!$target) {
                    throw new FeedSubNoFeedException($url);
                }
                return $this->discoverFromURL($target, false);
            }
        }

        return $this->initFromResponse($response);
    }

    function discoverFromFeedURL($url)
    {
        return $this->discoverFromURL($url, false);
    }

    function initFromResponse($response)
    {
        if (!$response->isOk()) {
            throw new FeedSubBadResponseException($response->getStatus());
        }

        $sourceurl = $response->getUrl();
        $body = $response->getBody();
        if (!$body) {
            throw new FeedSubEmptyException($sourceurl);
        }

        $type = $response->getHeader('Content-Type');
        if (preg_match('!^(text/xml|application/xml|application/(rss|atom)\+xml)!i', $type)) {
            return $this->init($sourceurl, $type, $body);
        } else {
            common_log(LOG_WARNING, "Unrecognized feed type $type for $sourceurl");
            throw new FeedSubUnrecognizedTypeException($type);
        }
    }

    function init($sourceurl, $type, $body)
    {
        $feed = new DOMDocument();
        if ($feed->loadXML($body)) {
            $this->uri = $sourceurl;
            $this->type = $type;
            $this->feed = $feed;

            $el = $this->feed->documentElement;

            // Looking for the "root" element: RSS channel or Atom feed

            if ($el->tagName == 'rss') {
                $channels = $el->getElementsByTagName('channel');
                if ($channels->length > 0) {
                    $this->root = $channels->item(0);
                } else {
                    throw new FeedSubBadXmlException($sourceurl);
                }
            } else if ($el->tagName == 'feed') {
                $this->root = $el;
            } else {
                throw new FeedSubBadXmlException($sourceurl);
            }

            return $this->uri;
        } else {
            throw new FeedSubBadXmlException($sourceurl);
        }
    }

    /**
     * @param string $url source URL, used to resolve relative links
     * @param string $body HTML body text
     * @return mixed string with URL or false if no target found
     */
    function discoverFromHTML($url, $body)
    {
        // DOMDocument::loadHTML may throw warnings on unrecognized elements,
        // and notices on unrecognized namespaces.
        $old = error_reporting(error_reporting() & ~(E_WARNING | E_NOTICE));
        $dom = new DOMDocument();
        $ok = $dom->loadHTML($body);
        error_reporting($old);

        if (!$ok) {
            throw new FeedSubBadHtmlException();
        }

        // Autodiscovery links may be relative to the page's URL or <base href>
        $base = false;
        $nodes = $dom->getElementsByTagName('base');
        for ($i = 0; $i < $nodes->length; $i++) {
            $node = $nodes->item($i);
            if ($node->hasAttributes()) {
                $href = $node->attributes->getNamedItem('href');
                if ($href) {
                    $base = trim($href->value);
                }
            }
        }
        if ($base) {
            $base = $this->resolveURI($base, $url);
        } else {
            $base = $url;
        }

        // Ok... now on to the links!
        // Types listed in order of priority -- we'll prefer Atom if available.
        // @fixme merge with the munger link checks
        $feeds = array(
            'application/atom+xml' => false,
            'application/rss+xml' => false,
        );

        $nodes = $dom->getElementsByTagName('link');
        for ($i = 0; $i < $nodes->length; $i++) {
            $node = $nodes->item($i);
            if ($node->hasAttributes()) {
                $rel = $node->attributes->getNamedItem('rel');
                $type = $node->attributes->getNamedItem('type');
                $href = $node->attributes->getNamedItem('href');
                if ($rel && $type && $href) {
                    $rel = array_filter(explode(" ", $rel->value));
                    $type = trim($type->value);
                    $href = trim($href->value);

                    if (in_array('alternate', $rel) && array_key_exists($type, $feeds) && empty($feeds[$type])) {
                        // Save the first feed found of each type...
                        $feeds[$type] = $this->resolveURI($href, $base);
                    }
                }
            }
        }

        // Return the highest-priority feed found
        foreach ($feeds as $type => $url) {
            if ($url) {
                return $url;
            }
        }

        return false;
    }

    /**
     * Resolve a possibly relative URL against some absolute base URL
     * @param string $rel relative or absolute URL
     * @param string $base absolute URL
     * @return string absolute URL, or original URL if could not be resolved.
     */
    function resolveURI($rel, $base)
    {
        require_once "Net/URL2.php";
        try {
            $relUrl = new Net_URL2($rel);
            if ($relUrl->isAbsolute()) {
                return $rel;
            }
            $baseUrl = new Net_URL2($base);
            $absUrl = $baseUrl->resolve($relUrl);
            return $absUrl->getURL();
        } catch (Exception $e) {
            common_log(LOG_WARNING, 'Unable to resolve relative link "' .
                $rel . '" against base "' . $base . '": ' . $e->getMessage());
            return $rel;
        }
    }
}


code:
<?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);
}

/**
 * Send a PuSH subscription verification from our internal hub.
 * @package Hub
 * @author Brion Vibber <brion@status.net>
 */
class HubConfQueueHandler extends QueueHandler
{
    function transport()
    {
        return 'hubconf';
    }

    function handle($data)
    {
        $sub = $data['sub'];
        $mode = $data['mode'];
        $token = $data['token'];

        assert($sub instanceof HubSub);
        assert($mode === 'subscribe' || $mode === 'unsubscribe');

        common_log(LOG_INFO, __METHOD__ . ": $mode $sub->callback $sub->topic");
        try {
            $sub->verify($mode, $token);
        } catch (Exception $e) {
            common_log(LOG_ERR, "Failed PuSH $mode verify to $sub->callback for $sub->topic: " .
                                $e->getMessage());
            // @fixme schedule retry?
            // @fixme just kill it?
        }

        return true;
    }
}


code:
<?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);
}

/**
 * Send a raw PuSH atom update from our internal hub.
 * @package Hub
 * @author Brion Vibber <brion@status.net>
 */
class HubOutQueueHandler extends QueueHandler
{
    function transport()
    {
        return 'hubout';
    }

    function handle($data)
    {
        $sub = $data['sub'];
        $atom = $data['atom'];
        $retries = $data['retries'];

        assert($sub instanceof HubSub);
        assert(is_string($atom));

        try {
            $sub->push($atom);
        } catch (Exception $e) {
            $retries--;
            $msg = "Failed PuSH to $sub->callback for $sub->topic: " .
                   $e->getMessage();
            if ($retries > 0) {
                common_log(LOG_ERR, "$msg; scheduling for $retries more tries");

                // @fixme when we have infrastructure to schedule a retry
                // after a delay, use it.
                $sub->distribute($atom, $retries);
            } else {
                common_log(LOG_ERR, "$msg; discarding");
            }
        }

        return true;
    }
}


code:
<?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);
}

/**
 * When we have a large batch of PuSH consumers, we break the data set
 * into smaller chunks. Enqueue final destinations...
 *
 * @package Hub
 * @author Brion Vibber <brion@status.net>
 */
class HubPrepQueueHandler extends QueueHandler
{
    // Enqueue this many low-level distributions before re-queueing the rest
    // of the batch to be processed later. Helps to keep latency down for other
    // things happening during a particularly long OStatus delivery session.
    //
    // [Could probably ditch this if we had working message delivery priorities
    // for queueing, but this isn't supported in ActiveMQ 5.3.]
    const ROLLING_BATCH = 20;

    function transport()
    {
        return 'hubprep';
    }

    function handle($data)
    {
        $topic = $data['topic'];
        $atom = $data['atom'];
        $pushCallbacks = $data['pushCallbacks'];

        assert(is_string($atom));
        assert(is_string($topic));
        assert(is_array($pushCallbacks));

        // Set up distribution for the first n subscribing sites...
        // If we encounter an uncatchable error, queue handling should
        // automatically re-run the batch, which could lead to some dupe
        // distributions.
        //
        // Worst case is if one of these hubprep entries dies too many
        // times and gets dropped; the rest of the batch won't get processed.
        try {
            $n = 0;
            while (count($pushCallbacks) && $n < self::ROLLING_BATCH) {
                $n++;
                $callback = array_shift($pushCallbacks);
                $sub = HubSub::getByHashkey($topic, $callback);
                if (!$sub) {
                    common_log(LOG_ERR, "Skipping PuSH delivery for deleted(?) consumer $callback on $topic");
                    continue;
                }

                $sub->distribute($atom);
            }
        } catch (Exception $e) {
            common_log(LOG_ERR, "Exception during PuSH batch out: " .
                                $e->getMessage() .
                                " prepping $topic to $callback");
        }

        // And re-queue the rest of the batch!
        if (count($pushCallbacks) > 0) {
            $sub = new HubSub();
            $sub->topic = $topic;
            $sub->bulkDistribute($atom, $pushCallbacks);
        }

        return true;
    }
}


code:
<?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 MagicEnvelope
{
    const ENCODING = 'base64url';

    const NS = 'http://salmon-protocol.org/ns/magic-env';

    protected $data      = null;    // When stored here it is _always_ base64url encoded
    protected $data_type = null;
    protected $encoding  = null;
    protected $alg       = null;
    protected $sig       = null;

    /**
     * Extract envelope data from an XML document containing an <me:env> or <me:provenance> element.
     *
     * @param string XML source
     * @return mixed associative array of envelope data, or false on unrecognized input
     *
     * @fixme will spew errors to logs or output in case of XML parse errors
     * @fixme may give fatal errors if some elements are missing or invalid XML
     * @fixme calling DOMDocument::loadXML statically triggers warnings in strict mode
     */
    public function __construct($xml=null) {
        if (!empty($xml)) {
            $dom = DOMDocument::loadXML($xml);
            if (!$dom instanceof DOMDocument) {
                throw new ServerException('Tried to load malformed XML as DOM');
            } elseif (!$this->fromDom($dom)) {
                throw new ServerException('Could not load MagicEnvelope from DOM');
            }
        }
    }

    /**
     * Retrieve Salmon keypair first by checking local database, but
     * if it's not found, attempt discovery if it has been requested.
     *
     * @param Profile $profile      The profile we're looking up keys for.
     * @param boolean $discovery    Network discovery if no local cache?
     */
    public function getKeyPair(Profile $profile, $discovery=false) {
        $magicsig = Magicsig::getKV('user_id', $profile->id);

        if ($discovery && !$magicsig instanceof Magicsig) {
            // Throws exception on failure, but does not try to _load_ the keypair string.
            $keypair = $this->discoverKeyPair($profile);

            $magicsig = new Magicsig();
            $magicsig->user_id = $profile->id;
            $magicsig->importKeys($keypair);
            // save the public key for this profile in our database.
            // TODO: If the profile generates a new key remotely, we must be able to replace
            //       this (of course after callback-verification).
            $magicsig->insert();
        } elseif (!$magicsig instanceof Magicsig) { // No discovery request, so we'll give up.
            throw new ServerException(sprintf('No public key found for profile (id==%d)', $profile->id));
        }

        assert($magicsig->publicKey instanceof Crypt_RSA);

        return $magicsig;
    }

    /**
     * Get the Salmon keypair from a URI, uses XRD Discovery etc. Reasonably
     * you'll only get the public key ;)
     *
     * The string will (hopefully) be formatted as described in Magicsig specification:
     * https://salmon-protocol.googlecode.com/svn/trunk/draft-panzer-magicsig-01.html#anchor13
     *
     * @return string formatted as Magicsig keypair
     */
    public function discoverKeyPair(Profile $profile)
    {
        $signer_uri = $profile->getUri();
        if (empty($signer_uri)) {
            throw new ServerException(sprintf('Profile missing URI (id==%d)', $profile->id));
        }

        $disco = new Discovery();

        // Throws exception on lookup problems
        $xrd = $disco->lookup($signer_uri);

        $link = $xrd->get(Magicsig::PUBLICKEYREL);
        if (is_null($link)) {
            // TRANS: Exception.
            throw new Exception(_m('Unable to locate signer public key.'));
        }

        // We have a public key element, let's hope it has proper key data.
        $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];
            }
        }

        if ($keypair === false) {
            // For debugging clarity. Keypair did not pass count()-check above.
            // TRANS: Exception when public key was not properly formatted.
            throw new Exception(_m('Incorrectly formatted public key element.'));
        }

        return $keypair;
    }

    /**
     * The current MagicEnvelope spec as used in StatusNet 0.9.7 and later
     * includes both the original data and some signing metadata fields as
     * the input plaintext for the signature hash.
     *
     * @return string
     */
    public function signingText() {
        return implode('.', array($this->data, // this field is pre-base64'd
                            Magicsig::base64_url_encode($this->data_type),
                            Magicsig::base64_url_encode($this->encoding),
                            Magicsig::base64_url_encode($this->alg)));
    }

    /**
     *
     * @param <type> $text
     * @param <type> $mimetype
     * @param Magicsig $magicsig    Magicsig with private key available.
     *
     * @return MagicEnvelope object with all properties set
     *
     * @throws Exception of various kinds on signing failure
     */
    public function signMessage($text, $mimetype, Magicsig $magicsig)
    {
        assert($magicsig->privateKey instanceof Crypt_RSA);

        // Prepare text and metadata for signing
        $this->data = Magicsig::base64_url_encode($text);
        $this->data_type = $mimetype;
        $this->encoding  = self::ENCODING;
        $this->alg       = $magicsig->getName();

        // Get the actual signature
        $this->sig = $magicsig->sign($this->signingText());
    }

    /**
     * Create an <me:env> XML representation of the envelope.
     *
     * @return string representation of XML document
     */
    public function toXML() {
        $xs = new XMLStringer();
        $xs->startXML();
        $xs->elementStart('me:env', array('xmlns:me' => self::NS));
        $xs->element('me:data', array('type' => $this->data_type), $this->data);
        $xs->element('me:encoding', null, $this->encoding);
        $xs->element('me:alg', null, $this->alg);
        $xs->element('me:sig', null, $this->getSignature());
        $xs->elementEnd('me:env');

        $string =  $xs->getString();
        return $string;
    }

    /*
     * Extract the contained XML payload, and insert a copy of the envelope
     * signature data as an <me:provenance> section.
     *
     * @return DOMDocument of Atom entry
     *
     * @fixme in case of XML parsing errors, this will spew to the error log or output
     */
    public function getPayload()
    {
        $dom = new DOMDocument();
        if (!$dom->loadXML(Magicsig::base64_url_decode($this->data))) {
            throw new ServerException('Malformed XML in Salmon payload');
        }

        switch ($this->data_type) {
        case 'application/atom+xml':
            if ($dom->documentElement->namespaceURI !== Activity::ATOM
                    || $dom->documentElement->tagName !== 'entry') {
                throw new ServerException(_m('Salmon post must be an Atom entry.'));
            }
            $prov = $dom->createElementNS(self::NS, 'me:provenance');
            $prov->setAttribute('xmlns:me', self::NS);
            $data = $dom->createElementNS(self::NS, 'me:data', $this->data);
            $data->setAttribute('type', $this->data_type);
            $prov->appendChild($data);
            $enc = $dom->createElementNS(self::NS, 'me:encoding', $this->encoding);
            $prov->appendChild($enc);
            $alg = $dom->createElementNS(self::NS, 'me:alg', $this->alg);
            $prov->appendChild($alg);
            $sig = $dom->createElementNS(self::NS, 'me:sig', $this->getSignature());
            $prov->appendChild($sig);

            $dom->documentElement->appendChild($prov);
            break;
        default:
            throw new ServerException('Unknown Salmon payload data type');
        }
        return $dom;
    }

    public function getSignature()
    {
        return $this->sig;
    }

    /**
     * Find the author URI referenced in the payload Atom entry.
     *
     * @return string URI for author
     * @throws ServerException on failure
     */
    public function getAuthorUri() {
        $doc = $this->getPayload();

        $authors = $doc->documentElement->getElementsByTagName('author');
        foreach ($authors as $author) {
            $uris = $author->getElementsByTagName('uri');
            foreach ($uris as $uri) {
                return $uri->nodeValue;
            }
        }
        throw new ServerException('No author URI found in Salmon payload data');
    }

    /**
     * Attempt to verify cryptographic signing for parsed envelope data.
     * Requires network access to retrieve public key referenced by the envelope signer.
     *
     * Details of failure conditions are dumped to output log and not exposed to caller.
     *
     * @param Profile $profile profile used to get locally cached public signature key
     *                         or if necessary perform discovery on.
     *
     * @return boolean
     */
    public function verify(Profile $profile)
    {
        if ($this->alg != 'RSA-SHA256') {
            common_log(LOG_DEBUG, "Salmon error: bad algorithm");
            return false;
        }

        if ($this->encoding != self::ENCODING) {
            common_log(LOG_DEBUG, "Salmon error: bad encoding");
            return false;
        }

        try {
            $magicsig = $this->getKeyPair($profile, true);    // Do discovery too if necessary
        } catch (Exception $e) {
            common_log(LOG_DEBUG, "Salmon error: ".$e->getMessage());
            return false;
        }

        return $magicsig->verify($this->signingText(), $this->getSignature());
    }

    /**
     * Extract envelope data from an XML document containing an <me:env> or <me:provenance> element.
     *
     * @param DOMDocument $dom
     * @return mixed associative array of envelope data, or false on unrecognized input
     *
     * @fixme may give fatal errors if some elements are missing
     */
    protected function fromDom(DOMDocument $dom)
    {
        $env_element = $dom->getElementsByTagNameNS(self::NS, 'env')->item(0);
        if (!$env_element) {
            $env_element = $dom->getElementsByTagNameNS(self::NS, 'provenance')->item(0);
        }

        if (!$env_element) {
            return false;
        }

        $data_element = $env_element->getElementsByTagNameNS(self::NS, 'data')->item(0);
        $sig_element = $env_element->getElementsByTagNameNS(self::NS, 'sig')->item(0);

        $this->data      = preg_replace('/\s/', '', $data_element->nodeValue);
        $this->data_type = $data_element->getAttribute('type');
        $this->encoding  = $env_element->getElementsByTagNameNS(self::NS, 'encoding')->item(0)->nodeValue;
        $this->alg       = $env_element->getElementsByTagNameNS(self::NS, 'alg')->item(0)->nodeValue;
        $this->sig       = preg_replace('/\s/', '', $sig_element->nodeValue);
        return true;
    }

    /**
     * Encode the given string as a signed MagicEnvelope XML document,
     * using the keypair for the given local user profile. We can of
     * course not sign a remote profile's slap, since we don't have the
     * private key.
     *
     * Side effects: will create and store a keypair on-demand if one
     * hasn't already been generated for this user. This can be very slow
     * on some systems.
     *
     * @param string $text XML fragment to sign, assumed to be Atom
     * @param User $user User who cryptographically signs $text
     *
     * @return MagicEnvelope object complete with signature
     *
     * @throws Exception on bad profile input or key generation problems
     */
    public static function signAsUser($text, User $user)
    {
        // Find already stored key
        $magicsig = Magicsig::getKV('user_id', $user->id);
        if (!$magicsig instanceof Magicsig) {
            $magicsig = Magicsig::generate($user);
        }
        assert($magicsig instanceof Magicsig);
        assert($magicsig->privateKey instanceof Crypt_RSA);

        $magic_env = new MagicEnvelope();
        $magic_env->signMessage($text, 'application/atom+xml', $magicsig);

        return $magic_env;
    }
}


code:
<?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);
}

/**
 * Prepare PuSH and Salmon distributions for an outgoing message.
 *
 * @package OStatusPlugin
 * @author Brion Vibber <brion@status.net>
 */
class OStatusQueueHandler extends QueueHandler
{
    // If we have more than this many subscribing sites on a single feed,
    // break up the PuSH distribution into smaller batches which will be
    // rolled into the queue progressively. This reduces disruption to
    // other, shorter activities being enqueued while we work.
    const MAX_UNBATCHED = 50;

    // Each batch (a 'hubprep' entry) will have this many items.
    // Selected to provide a balance between queue packet size
    // and number of batches that will end up getting processed.
    // For 20,000 target sites, 1000 should work acceptably.
    const BATCH_SIZE = 1000;

    function transport()
    {
        return 'ostatus';
    }

    function handle($notice)
    {
        assert($notice instanceof Notice);

        $this->notice = $notice;
        $this->user = User::getKV('id', $notice->profile_id);

        try {
            $profile = $this->notice->getProfile();
        } catch (Exception $e) {
            common_log(LOG_ERR, "Can't get profile for notice; skipping: " . $e->getMessage());
            return true;
        }

        if ($notice->isLocal()) {
            // Notices generated on remote sites will have already
            // been pushed to user's subscribers by their origin sites.
            $this->pushUser();
        }

        foreach ($notice->getGroups() as $group) {
            $oprofile = Ostatus_profile::getKV('group_id', $group->id);
            if ($oprofile) {
                // remote group
                if ($notice->isLocal()) {
                    $this->pingReply($oprofile);
                }
            } else {
                // local group
                $this->pushGroup($group->id);
            }
        }

        if ($notice->isLocal()) {
            // Notices generated on other sites will have already
            // pinged their reply-targets.

            foreach ($notice->getReplies() as $profile_id) {
                $oprofile = Ostatus_profile::getKV('profile_id', $profile_id);
                if ($oprofile) {
                    $this->pingReply($oprofile);
                }
            }

            if (!empty($this->notice->reply_to)) {
                $replyTo = Notice::getKV('id', $this->notice->reply_to);
                if (!empty($replyTo)) {
                    foreach($replyTo->getReplies() as $profile_id) {
                        $oprofile = Ostatus_profile::getKV('profile_id', $profile_id);
                        if ($oprofile) {
                            $this->pingReply($oprofile);
                        }
                    }
                }
            }

            foreach ($notice->getProfileTags() as $ptag) {
                $oprofile = Ostatus_profile::getKV('peopletag_id', $ptag->id);
                if (!$oprofile) {
                    $this->pushPeopletag($ptag);
                }
            }
        }

        return true;
    }

    function pushUser()
    {
        if ($this->user) {
            // For local posts, ping the PuSH hub to update their feed.
            // http://identi.ca/api/statuses/user_timeline/1.atom
            $feed = common_local_url('ApiTimelineUser',
                                     array('id' => $this->user->id,
                                           'format' => 'atom'));
            $this->pushFeed($feed, array($this, 'userFeedForNotice'));
        }
    }

    function pushGroup($group_id)
    {
        // For a local group, ping the PuSH hub to update its feed.
        // Updates may come from either a local or a remote user.
        $feed = common_local_url('ApiTimelineGroup',
                                 array('id' => $group_id,
                                       'format' => 'atom'));
        $this->pushFeed($feed, array($this, 'groupFeedForNotice'), $group_id);
    }

    function pushPeopletag($ptag)
    {
        // For a local people tag, ping the PuSH hub to update its feed.
        // Updates may come from either a local or a remote user.
        $feed = common_local_url('ApiTimelineList',
                                 array('id' => $ptag->id,
                                       'user' => $ptag->tagger,
                                       'format' => 'atom'));
        $this->pushFeed($feed, array($this, 'peopletagFeedForNotice'), $ptag);
    }

    function pingReply($oprofile)
    {
        if ($this->user) {
            // For local posts, send a Salmon ping to the mentioned
            // remote user or group.
            // @fixme as an optimization we can skip this if the
            // remote profile is subscribed to the author.
            $oprofile->notifyDeferred($this->notice, $this->user);
        }
    }

    /**
     * @param string $feed URI to the feed
     * @param callable $callback function to generate Atom feed update if needed
     *        any additional params are passed to the callback.
     */
    function pushFeed($feed, $callback)
    {
        $hub = common_config('ostatus', 'hub');
        if ($hub) {
            $this->pushFeedExternal($feed, $hub);
        }

        $sub = new HubSub();
        $sub->topic = $feed;
        if ($sub->find()) {
            $args = array_slice(func_get_args(), 2);
            $atom = call_user_func_array($callback, $args);
            $this->pushFeedInternal($atom, $sub);
        } else {
            common_log(LOG_INFO, "No PuSH subscribers for $feed");
        }
        return true;
    }

    /**
     * Ping external hub about this update.
     * The hub will pull the feed and check for new items later.
     * Not guaranteed safe in an environment with database replication.
     *
     * @param string $feed feed topic URI
     * @param string $hub PuSH hub URI
     * @fixme can consolidate pings for user & group posts
     */
    function pushFeedExternal($feed, $hub)
    {
        $client = new HTTPClient();
        try {
            $data = array('hub.mode' => 'publish',
                          'hub.url' => $feed);
            $response = $client->post($hub, array(), $data);
            if ($response->getStatus() == 204) {
                common_log(LOG_INFO, "PuSH ping to hub $hub for $feed ok");
                return true;
            } else {
                common_log(LOG_ERR, "PuSH ping to hub $hub for $feed failed with HTTP " .
                                    $response->getStatus() . ': ' .
                                    $response->getBody());
            }
        } catch (Exception $e) {
            common_log(LOG_ERR, "PuSH ping to hub $hub for $feed failed: " . $e->getMessage());
            return false;
        }
    }

    /**
     * Queue up direct feed update pushes to subscribers on our internal hub.
     * If there are a large number of subscriber sites, intermediate bulk
     * distribution triggers may be queued.
     *
     * @param string $atom update feed, containing only new/changed items
     * @param HubSub $sub open query of subscribers
     */
    function pushFeedInternal($atom, $sub)
    {
        common_log(LOG_INFO, "Preparing $sub->N PuSH distribution(s) for $sub->topic");
        $n = 0;
        $batch = array();
        while ($sub->fetch()) {
            $n++;
            if ($n < self::MAX_UNBATCHED) {
                $sub->distribute($atom);
            } else {
                $batch[] = $sub->callback;
                if (count($batch) >= self::BATCH_SIZE) {
                    $sub->bulkDistribute($atom, $batch);
                    $batch = array();
                }
            }
        }
        if (count($batch) >= 0) {
            $sub->bulkDistribute($atom, $batch);
        }
    }

    /**
     * Build a single-item version of the sending user's Atom feed.
     * @return string
     */
    function userFeedForNotice()
    {
        $atom = new AtomUserNoticeFeed($this->user);
        $atom->addEntryFromNotice($this->notice);
        $feed = $atom->getString();

        return $feed;
    }

    function groupFeedForNotice($group_id)
    {
        $group = User_group::getKV('id', $group_id);

        $atom = new AtomGroupNoticeFeed($group);
        $atom->addEntryFromNotice($this->notice);
        $feed = $atom->getString();

        return $feed;
    }

    function peopletagFeedForNotice($ptag)
    {
        $atom = new AtomListNoticeFeed($ptag);
        $atom->addEntryFromNotice($this->notice);
        $feed = $atom->getString();

        return $feed;
    }
}


code:
<?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);
}

/**
 * Process a feed distribution POST from a PuSH hub.
 * @package FeedSub
 * @author Brion Vibber <brion@status.net>
 */
class PushInQueueHandler extends QueueHandler
{
    function transport()
    {
        return 'pushin';
    }

    function handle($data)
    {
        assert(is_array($data));

        $feedsub_id = $data['feedsub_id'];
        $post = $data['post'];
        $hmac = $data['hmac'];

        $feedsub = FeedSub::getKV('id', $feedsub_id);
        if ($feedsub instanceof FeedSub) {
            try {
                $feedsub->receive($post, $hmac);
            } catch(Exception $e) {
                common_log(LOG_ERR, "Exception during PuSH input processing for $feedsub->uri: " . $e->getMessage());
            }
        } else {
            common_log(LOG_ERR, "Discarding POST to unknown feed subscription id $feedsub_id");
        }
        return true;
    }
}


code:
<?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 Salmon
{
    const REL_SALMON = 'salmon';
    const REL_MENTIONED = 'mentioned';

    // XXX: these are deprecated
    const NS_REPLIES = "http://salmon-protocol.org/ns/salmon-replies";
    const NS_MENTIONS = "http://salmon-protocol.org/ns/salmon-mention";

    /**
     * Sign and post the given Atom entry as a Salmon message.
     *
     * Side effects: may generate a keypair on-demand for the given user,
     * which can be very slow on some systems (like those without php5-gmp).
     *
     * @param string $endpoint_uri
     * @param string $xml string representation of payload
     * @param User $user local user profile whose keys we sign with
     * @return boolean success
     */
    public static function post($endpoint_uri, $xml, User $user)
    {
        if (empty($endpoint_uri)) {
            common_debug('No endpoint URI for Salmon post to '.$user->getUri());
            return false;
        }

        try {
            $magic_env = MagicEnvelope::signAsUser($xml, $user);
            $envxml = $magic_env->toXML();
        } catch (Exception $e) {
            common_log(LOG_ERR, "Salmon unable to sign: " . $e->getMessage());
            return false;
        }

        $headers = array('Content-Type: application/magic-envelope+xml');

        try {
            $client = new HTTPClient();
            $client->setBody($envxml);
            $response = $client->post($endpoint_uri, $headers);
        } catch (HTTP_Request2_Exception $e) {
            common_log(LOG_ERR, "Salmon post to $endpoint_uri failed: " . $e->getMessage());
            return false;
        }
        if ($response->getStatus() != 200) {
            common_log(LOG_ERR, sprintf('Salmon (from profile %d) endpoint %s returned status %s: %s',
                                $user->id, $endpoint_uri, $response->getStatus(), $response->getBody()));
            return false;
        }

        // Success!
        return true;
    }
}


code:
<?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
 * @author James Walker <james@status.net>
 */

if (!defined('GNUSOCIAL')) { exit(1); }

class SalmonAction extends Action
{
    protected $needPost = true;

    protected $oprofile = null; // Ostatus_profile of the actor
    protected $actor    = null; // Profile object of the actor

    var $xml      = null;
    var $activity = null;
    var $target   = null;

    protected function prepare(array $args=array())
    {
        GNUsocial::setApi(true); // Send smaller error pages

        parent::prepare($args);

        if (!isset($_SERVER['CONTENT_TYPE']) || $_SERVER['CONTENT_TYPE'] != 'application/magic-envelope+xml') {
            // TRANS: Client error. Do not translate "application/magic-envelope+xml".
            $this->clientError(_m('Salmon requires "application/magic-envelope+xml".'));
        }

        try {
            $envxml = file_get_contents('php://input');
            $magic_env = new MagicEnvelope($envxml);   // parse incoming XML as a MagicEnvelope

            $entry = $magic_env->getPayload();  // Not cryptographically verified yet!
            $this->activity = new Activity($entry->documentElement);
            if (empty($this->activity->actor->id)) {
                common_log(LOG_ERR, "broken actor: " . var_export($this->activity->actor->id, true));
                common_log(LOG_ERR, "activity with no actor: " . var_export($this->activity, true));
                // TRANS: Exception.
                throw new Exception(_m('Received a salmon slap from unidentified actor.'));
            }
            // ensureProfiles sets $this->actor and $this->oprofile
            $this->ensureProfiles();
        } catch (Exception $e) {
            common_debug('Salmon envelope parsing failed with: '.$e->getMessage());
            $this->clientError($e->getMessage());
        }

        // Cryptographic verification test
        if (!$magic_env->verify($this->actor)) {
            common_log(LOG_DEBUG, "Salmon signature verification failed.");
            // TRANS: Client error.
            $this->clientError(_m('Salmon signature verification failed.'));
        }

        return true;
    }

    /**
     * Check the posted activity type and break out to appropriate processing.
     */

    protected function handle()
    {
        parent::handle();

        common_log(LOG_DEBUG, "Got a " . $this->activity->verb);
        try {
            if (Event::handle('StartHandleSalmonTarget', array($this->activity, $this->target)) &&
                    Event::handle('StartHandleSalmon', array($this->activity))) {
                switch ($this->activity->verb) {
                case ActivityVerb::POST:
                    $this->handlePost();
                    break;
                case ActivityVerb::SHARE:
                    $this->handleShare();
                    break;
                case ActivityVerb::FOLLOW:
                case ActivityVerb::FRIEND:
                    $this->handleFollow();
                    break;
                case ActivityVerb::UNFOLLOW:
                    $this->handleUnfollow();
                    break;
                case ActivityVerb::JOIN:
                    $this->handleJoin();
                    break;
                case ActivityVerb::LEAVE:
                    $this->handleLeave();
                    break;
                case ActivityVerb::TAG:
                    $this->handleTag();
                    break;
                case ActivityVerb::UNTAG:
                    $this->handleUntag();
                    break;
                case ActivityVerb::UPDATE_PROFILE:
                    $this->handleUpdateProfile();
                    break;
                default:
                    // TRANS: Client exception.
                    throw new ClientException(_m('Unrecognized activity type.'));
                }
                Event::handle('EndHandleSalmon', array($this->activity));
                Event::handle('EndHandleSalmonTarget', array($this->activity, $this->target));
            }
        } catch (AlreadyFulfilledException $e) {
            // The action's results are already fulfilled. Maybe it was a
            // duplicate? Maybe someone's database is out of sync?
            // Let's just accept it and move on.
            common_log(LOG_INFO, 'Salmon slap carried an event which had already been fulfilled.');
        }
    }

    function handlePost()
    {
        // TRANS: Client exception.
        throw new ClientException(_m('This target does not understand posts.'));
    }

    function handleFollow()
    {
        // TRANS: Client exception.
        throw new ClientException(_m('This target does not understand follows.'));
    }

    function handleUnfollow()
    {
        // TRANS: Client exception.
        throw new ClientException(_m('This target does not understand unfollows.'));
    }

    function handleShare()
    {
        // TRANS: Client exception.
        throw new ClientException(_m('This target does not understand share events.'));
    }

    function handleJoin()
    {
        // TRANS: Client exception.
        throw new ClientException(_m('This target does not understand joins.'));
    }

    function handleLeave()
    {
        // TRANS: Client exception.
        throw new ClientException(_m('This target does not understand leave events.'));
    }

    function handleTag()
    {
        // TRANS: Client exception.
        throw new ClientException(_m('This target does not understand list events.'));
    }

    function handleUntag()
    {
        // TRANS: Client exception.
        throw new ClientException(_m('This target does not understand unlist events.'));
    }

    /**
     * Remote user sent us an update to their profile.
     * If we already know them, accept the updates.
     */
    function handleUpdateProfile()
    {
        $oprofile = Ostatus_profile::getActorProfile($this->activity);
        if ($oprofile instanceof Ostatus_profile) {
            common_log(LOG_INFO, "Got a profile-update ping from $oprofile->uri");
            $oprofile->updateFromActivityObject($this->activity->actor);
        } else {
            common_log(LOG_INFO, "Ignoring profile-update ping from unknown " . $this->activity->actor->id);
        }
    }

    function ensureProfiles()
    {
        try {
            $this->oprofile = Ostatus_profile::getActorProfile($this->activity);
            if (!$this->oprofile instanceof Ostatus_profile) {
                throw new UnknownUriException($this->activity->actor->id);
            }
        } catch (UnknownUriException $e) {
            // Apparently we didn't find the Profile object based on our URI,
            // so OStatus doesn't have it with this URI in ostatus_profile.
            // Try to look it up again, remote side may have changed from http to https
            // or maybe publish an acct: URI now instead of an http: URL.
            //
            // Steps:
            // 1. Check the newly received URI. Who does it say it is?
            // 2. Compare these alleged identities to our local database.
            // 3. If we found any locally stored identities, ask it about its aliases.
            // 4. Do any of the aliases from our known identity match the recently introduced one?
            //
            // Example: We have stored http://example.com/user/1 but this URI says https://example.com/user/1
            common_debug('No local Profile object found for a magicsigned activity author URI: '.$e->object_uri);
            $disco = new Discovery();
            $xrd = $disco->lookup($e->object_uri);
            // Step 1: We got a bunch of discovery data for https://example.com/user/1 which includes
            //         aliases https://example.com/user and hopefully our original http://example.com/user/1 too
            $all_ids = array_merge(array($xrd->subject), $xrd->aliases);

            if (!in_array($e->object_uri, $all_ids)) {
                common_debug('The activity author URI we got was not listed itself when doing discovery on it.');
                throw $e;
            }

            // Go through each reported alias from lookup to see if we know this already
            foreach ($all_ids as $aliased_uri) {
                $oprofile = Ostatus_profile::getKV('uri', $aliased_uri);
                if (!$oprofile instanceof Ostatus_profile) {
                    continue;   // unknown locally, check the next alias
                }
                // Step 2: We found the alleged http://example.com/user/1 URI in our local database,
                //         but this can't be trusted yet because anyone can publish any alias.
                common_debug('Found a local Ostatus_profile for "'.$e->object_uri.'" with this URI: '.$aliased_uri);

                // We found an existing OStatus profile, but is it really the same? Do a callback to the URI's origin
                // Step 3: lookup our previously known http://example.com/user/1 webfinger etc.
                $xrd = $disco->lookup($oprofile->getUri()); // getUri returns ->uri, which we filtered on earlier
                $doublecheck_aliases = array_merge(array($xrd->subject), $xrd->aliases);
                common_debug('Trying to match known "'.$aliased_uri.'" against its returned aliases: '.implode(' ', $doublecheck_aliases));
                // if we find our original URI here, it is a legitimate alias
                // Step 4: Is the newly introduced https://example.com/user/1 URI in the list of aliases
                //         presented by http://example.com/user/1 (i.e. do they both say they are the same identity?)
                if (in_array($e->object_uri, $doublecheck_aliases)) {
                    common_debug('URIFIX These identities both say they are each other: "'.$aliased_uri.'" and "'.$e->object_uri.'"');
                    $orig = clone($oprofile);
                    $oprofile->uri = $e->object_uri;
                    common_debug('URIFIX Updating Ostatus_profile URI for '.$aliased_uri.' to '.$oprofile->uri);
                    $oprofile->updateWithKeys($orig, 'uri');    // 'uri' is the primary key column
                    unset($orig);
                    $this->oprofile = $oprofile;
                    break;  // don't iterate through aliases anymore
                }
            }

            // We might end up here after $all_ids is iterated through without a $this->oprofile value,
            if (!$this->oprofile instanceof Ostatus_profile) {
                common_debug("We do not have a local profile to connect to this activity's author. Let's create one.");
                // ensureActivityObjectProfile throws exception on failure
                $this->oprofile = Ostatus_profile::ensureActivityObjectProfile($this->activity->actor);
            }
        }

        assert($this->oprofile instanceof Ostatus_profile);

        $this->actor = $this->oprofile->localProfile();
    }

    function saveNotice()
    {
        if (!$this->oprofile instanceof Ostatus_profile) {
            common_debug('Ostatus_profile missing in ' . get_class(). ' profile: '.var_export($this->profile));
        }
        return $this->oprofile->processPost($this->activity, 'salmon');
    }
}


code:
<?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);
}

/**
 * Send a Salmon notification in the background.
 * @package OStatusPlugin
 * @author Brion Vibber <brion@status.net>
 */
class SalmonQueueHandler extends QueueHandler
{
    function transport()
    {
        return 'salmon';
    }

    function handle($data)
    {
        assert(is_array($data));
        assert(is_string($data['salmonuri']));
        assert(is_string($data['entry']));

        $actor = Profile::getKV($data['actor']);

        Salmon::post($data['salmonuri'], $data['entry'], $actor->getUser());

        // @fixme detect failure and attempt to resend
        return true;
    }
}


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-27 16:31+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#. TRANS: Link to subscribe to a remote entity.
#. TRANS: Link text for a user to subscribe to an OStatus user.
#: OStatusPlugin.php:206 OStatusPlugin.php:227 OStatusPlugin.php:1196
msgid "Subscribe"
msgstr ""

#. TRANS: Exception in OStatus when invalid URI was entered.
#: OStatusPlugin.php:249 OStatusPlugin.php:284
msgid "Invalid URI."
msgstr ""

#. TRANS: Error message in OStatus plugin. Do not translate the domain names example.com
#. TRANS: and example.net, as these are official standard domain names for use in examples.
#: OStatusPlugin.php:258 OStatusPlugin.php:291 actions/ostatussub.php:253
#: actions/ostatussub.php:261 actions/ostatussub.php:287
msgid ""
"Sorry, we could not reach that address. Please make sure that the OStatus "
"address is like nickname@example.com or http://example.net/nickname."
msgstr ""

#. TRANS: Title. %s is a domain name.
#: OStatusPlugin.php:519
#, php-format
msgid "Sent from %s via OStatus"
msgstr ""

#. TRANS: Title for unfollowing a remote profile.
#: OStatusPlugin.php:657
msgctxt "TITLE"
msgid "Unfollow"
msgstr ""

#. TRANS: Success message for unsubscribe from user attempt through OStatus.
#. TRANS: %1$s is the unsubscriber's name, %2$s is the unsubscribed user's name.
#: OStatusPlugin.php:660
#, php-format
msgid "%1$s stopped following %2$s."
msgstr ""

#. TRANS: Title for joining a remote groep.
#: OStatusPlugin.php:707
msgctxt "TITLE"
msgid "Join"
msgstr ""

#. TRANS: Success message for subscribe to group attempt through OStatus.
#. TRANS: %1$s is the member name, %2$s is the subscribed group's name.
#: OStatusPlugin.php:710
#, php-format
msgid "%1$s has joined group %2$s."
msgstr ""

#. TRANS: Exception thrown when joining a remote group fails.
#: OStatusPlugin.php:719
msgid "Failed joining remote group."
msgstr ""

#. TRANS: Title for leaving a remote group.
#: OStatusPlugin.php:761
msgctxt "TITLE"
msgid "Leave"
msgstr ""

#. TRANS: Success message for unsubscribe from group attempt through OStatus.
#. TRANS: %1$s is the member name, %2$s is the unsubscribed group's name.
#: OStatusPlugin.php:764
#, php-format
msgid "%1$s has left group %2$s."
msgstr ""

#. TRANS: Title for following a remote list.
#: OStatusPlugin.php:807
msgctxt "TITLE"
msgid "Follow list"
msgstr ""

#. TRANS: Success message for remote list follow through OStatus.
#. TRANS: %1$s is the subscriber name, %2$s is the list, %3$s is the lister's name.
#: OStatusPlugin.php:810
#, php-format
msgid "%1$s is now following people listed in %2$s by %3$s."
msgstr ""

#. TRANS: Exception thrown when subscription to remote list fails.
#: OStatusPlugin.php:820
msgid "Failed subscribing to remote list."
msgstr ""

#. TRANS: Title for unfollowing a remote list.
#: OStatusPlugin.php:859
msgid "Unfollow list"
msgstr ""

#. TRANS: Success message for remote list unfollow through OStatus.
#. TRANS: %1$s is the subscriber name, %2$s is the list, %3$s is the lister's name.
#: OStatusPlugin.php:862
#, php-format
msgid "%1$s stopped following the list %2$s by %3$s."
msgstr ""

#. TRANS: Title for listing a remote profile.
#: OStatusPlugin.php:938
msgctxt "TITLE"
msgid "List"
msgstr ""

#. TRANS: Success message for remote list addition through OStatus.
#. TRANS: %1$s is the list creator's name, %2$s is the added list member, %3$s is the list name.
#: OStatusPlugin.php:941
#, php-format
msgid "%1$s listed %2$s in the list %3$s."
msgstr ""

#. TRANS: Title for unlisting a remote profile.
#: OStatusPlugin.php:989
msgctxt "TITLE"
msgid "Unlist"
msgstr ""

#. TRANS: Success message for remote list removal through OStatus.
#. TRANS: %1$s is the list creator's name, %2$s is the removed list member, %3$s is the list name.
#: OStatusPlugin.php:992
#, php-format
msgid "%1$s removed %2$s from the list %3$s."
msgstr ""

#. TRANS: Title for unliking a remote notice.
#: OStatusPlugin.php:1039
msgid "Unlike"
msgstr ""

#. TRANS: Success message for remove a favorite notice through OStatus.
#. TRANS: %1$s is the unfavoring user's name, %2$s is URI to the no longer favored notice.
#: OStatusPlugin.php:1042
#, php-format
msgid "%1$s no longer likes %2$s."
msgstr ""

#. TRANS: Link text for link to remote subscribe.
#: OStatusPlugin.php:1118
msgid "Remote"
msgstr ""

#. TRANS: Title for activity.
#: OStatusPlugin.php:1158
msgid "Profile update"
msgstr ""

#. TRANS: Ping text for remote profile update through OStatus.
#. TRANS: %s is user that updated their profile.
#: OStatusPlugin.php:1161
#, php-format
msgid "%s has updated their profile page."
msgstr ""

#. TRANS: Link text for a user to list an OStatus user.
#: OStatusPlugin.php:1205
msgid "List"
msgstr ""

#. TRANS: Plugin description.
#: OStatusPlugin.php:1220
msgid ""
"Follow people across social networks that implement OStatus."
msgstr ""

#. TRANS: Client exception.
#: actions/pushhub.php:66
msgid "Publishing outside feeds not supported."
msgstr ""

#. TRANS: Client exception. %s is a mode.
#: actions/pushhub.php:69
#, php-format
msgid "Unrecognized mode \"%s\"."
msgstr ""

#. TRANS: Client exception. %s is a topic.
#: actions/pushhub.php:89
#, php-format
msgid ""
"Unsupported hub.topic %s this hub only serves local user and group Atom "
"feeds."
msgstr ""

#. TRANS: Client exception. %s is the invalid lease value.
#: actions/pushhub.php:95
#, php-format
msgid "Invalid hub.lease \"%s\". It must be empty or positive integer."
msgstr ""

#. TRANS: Client exception. %s is the invalid hub secret.
#: actions/pushhub.php:101
#, php-format
msgid "Invalid hub.secret \"%s\". It must be under 200 bytes."
msgstr ""

#. TRANS: Client exception. %s is a feed URL.
#: actions/pushhub.php:153
#, php-format
msgid "Invalid hub.topic \"%s\". User does not exist."
msgstr ""

#. TRANS: Client exception. %s is a feed URL.
#: actions/pushhub.php:161
#, php-format
msgid "Invalid hub.topic \"%s\". Local_group does not exist."
msgstr ""

#. TRANS: Client exception. %s is a feed URL.
#: actions/pushhub.php:182
#, php-format
msgid "Invalid hub.topic %s; list does not exist."
msgstr ""

#. TRANS: Client exception.
#. TRANS: %1$s is this argument to the method this exception occurs in, %2$s is a URL.
#: actions/pushhub.php:209
#, php-format
msgid "Invalid URL passed for %1$s: \"%2$s\""
msgstr ""

#. TRANS: Field label.
#: actions/ostatusgroup.php:74
msgid "Join group"
msgstr ""

#. TRANS: Tooltip for field label "Join group". Do not translate the "example.net"
#. TRANS: domain name in the URL, as it is an official standard domain name for examples.
#: actions/ostatusgroup.php:78
msgid "OStatus group's address, like http://example.net/group/nickname."
msgstr ""

#. TRANS: Button text.
#. TRANS: Button text to continue joining a remote list.
#. TRANS: Button text.
#: actions/ostatusgroup.php:83 actions/ostatuspeopletag.php:82
#: actions/ostatussub.php:101
msgctxt "BUTTON"
msgid "Continue"
msgstr ""

#. TRANS: Error text displayed when trying to join a remote group the user is already a member of.
#: actions/ostatusgroup.php:101
msgid "You are already a member of this group."
msgstr ""

#. TRANS: OStatus remote group subscription dialog error.
#: actions/ostatusgroup.php:134
msgid "Already a member!"
msgstr ""

#. TRANS: OStatus remote group subscription dialog error.
#: actions/ostatusgroup.php:144
msgid "Remote group join failed!"
msgstr ""

#. TRANS: Page title for OStatus remote group join form
#: actions/ostatusgroup.php:159
msgid "Confirm joining remote group"
msgstr ""

#. TRANS: Form instructions.
#: actions/ostatusgroup.php:170
msgid ""
"You can subscribe to groups from other supported sites. Paste the group's "
"profile URI below:"
msgstr ""

#. TRANS: Client error displayed when trying to list a local object as if it is remote.
#: actions/ostatustag.php:40
msgid "You can use the local list functionality!"
msgstr ""

#. TRANS: Header for listing a remote object. %s is a remote object's name.
#: actions/ostatustag.php:54
#, php-format
msgid "List %s"
msgstr ""

#. TRANS: Button text to list a remote object.
#: actions/ostatustag.php:56
msgctxt "BUTTON"
msgid "Go"
msgstr ""

#. TRANS: Field label.
#: actions/ostatustag.php:68 actions/ostatusinit.php:134
msgid "User nickname"
msgstr ""

#. TRANS: Field title.
#: actions/ostatustag.php:70
msgid "Nickname of the user you want to list."
msgstr ""

#. TRANS: Field label.
#: actions/ostatustag.php:74 actions/ostatusinit.php:144
msgid "Profile Account"
msgstr ""

#. TRANS: Field title.
#: actions/ostatustag.php:76
msgid "Your account id (for example user@example.com)."
msgstr ""

#. TRANS: Client error displayed when remote profile address could not be confirmed.
#. TRANS: Client error.
#: actions/ostatustag.php:103 actions/ostatusinit.php:191
msgid "Could not confirm remote profile address."
msgstr ""

#. TRANS: Title for an OStatus list.
#: actions/ostatustag.php:121
msgid "OStatus list"
msgstr ""

#. TRANS: Client error displayed trying to perform an action without providing an ID.
#. TRANS: Client error.
#: actions/peopletagsalmon.php:38 actions/usersalmon.php:36
#: actions/groupsalmon.php:38
msgid "No ID."
msgstr ""

#. TRANS: Client error displayed when referring to a non-existing list.
#. TRANS: Client error.
#: actions/peopletagsalmon.php:45 actions/ostatusinit.php:240
msgid "No such list."
msgstr ""

#. TRANS: Client error displayed when trying to send a message to a remote list.
#: actions/peopletagsalmon.php:54
msgid "Cannot accept remote posts for a remote list."
msgstr ""

#. TRANS: Client error displayed when trying to subscribe a group to a list.
#. TRANS: Client error displayed when trying to unsubscribe a group from a list.
#: actions/peopletagsalmon.php:91 actions/peopletagsalmon.php:124
msgid "Groups cannot subscribe to lists."
msgstr ""

#. TRANS: Server error displayed when subscribing a remote user to a list fails.
#. TRANS: %1$s is a profile URI, %2$s is a list name.
#: actions/peopletagsalmon.php:109
#, php-format
msgid "Could not subscribe remote user %1$s to list %2$s."
msgstr ""

#. TRANS: Client error displayed when trying to unsubscribe a remote user from a list fails.
#. TRANS: %1$s is a profile URL, %2$s is a list name.
#: actions/peopletagsalmon.php:133
#, php-format
msgid "Could not unsubscribe remote user %1$s from list %2$s."
msgstr ""

#. TRANS: Client error.
#: actions/ostatusinit.php:44
msgid "You can use the local subscription!"
msgstr ""

#. TRANS: Client error displayed when the session token does not match or is not given.
#: actions/ostatusinit.php:68 actions/ostatussub.php:339
msgid "There was a problem with your session token. Try again, please."
msgstr ""

#. TRANS: Form title.
#: actions/ostatusinit.php:85
msgctxt "TITLE"
msgid "Subscribe to user"
msgstr ""

#. TRANS: Form legend. %s is a group name.
#: actions/ostatusinit.php:101
#, php-format
msgid "Join group %s"
msgstr ""

#. TRANS: Button text to join a group.
#: actions/ostatusinit.php:103
msgctxt "BUTTON"
msgid "Join"
msgstr ""

#. TRANS: Form legend. %1$s is a list, %2$s is a lister's name.
#: actions/ostatusinit.php:106
#, php-format
msgid "Subscribe to list %1$s by %2$s"
msgstr ""

#. TRANS: Button text to subscribe to a list.
#. TRANS: Button text to subscribe to a profile.
#: actions/ostatusinit.php:108 actions/ostatusinit.php:114
msgctxt "BUTTON"
msgid "Subscribe"
msgstr ""

#. TRANS: Form legend. %s is a nickname.
#: actions/ostatusinit.php:112
#, php-format
msgid "Subscribe to %s"
msgstr ""

#. TRANS: Field label.
#: actions/ostatusinit.php:129
msgid "Group nickname"
msgstr ""

#. TRANS: Field title.
#: actions/ostatusinit.php:131
msgid "Nickname of the group you want to join."
msgstr ""

#. TRANS: Field title.
#: actions/ostatusinit.php:136
msgid "Nickname of the user you want to follow."
msgstr ""

#. TRANS: Tooltip for field label "Profile Account".
#: actions/ostatusinit.php:146
msgid "Your account ID (e.g. user@example.com)."
msgstr ""

#. TRANS: Client error.
#: actions/ostatusinit.php:168
msgid "Must provide a remote profile."
msgstr ""

#. TRANS: Client error.
#. TRANS: Client error displayed when referring to a non-existing user.
#: actions/ostatusinit.php:217 actions/ostatusinit.php:231
#: actions/usersalmon.php:43
msgid "No such user."
msgstr ""

#. TRANS: Client error.
#: actions/ostatusinit.php:225 actions/groupsalmon.php:45
msgid "No such group."
msgstr ""

#. TRANS: Client error.
#: actions/ostatusinit.php:243
msgid "No local user or group nickname provided."
msgstr ""

#. TRANS: Page title.
#: actions/ostatusinit.php:250
msgid "OStatus Connect"
msgstr ""

#. TRANS: Server exception thrown when referring to a non-existing or empty feed.
#: actions/pushcallback.php:51
msgid "Empty or invalid feed id."
msgstr ""

#. TRANS: Server exception. %s is a feed ID.
#: actions/pushcallback.php:57
#, php-format
msgid "Unknown PuSH feed id %s"
msgstr ""

#. TRANS: Client exception. %s is an invalid feed name.
#: actions/pushcallback.php:96
#, php-format
msgid "Bad hub.topic feed \"%s\"."
msgstr ""

#. TRANS: Client exception. %s is an invalid topic.
#: actions/pushcallback.php:103
#, php-format
msgid "Unexpected subscribe request for %s."
msgstr ""

#. TRANS: Client exception. %s is an invalid topic.
#: actions/pushcallback.php:108
#, php-format
msgid "Unexpected unsubscribe request for %s."
msgstr ""

#. TRANS: Client exception thrown when an undefied activity is performed.
#. TRANS: Client exception.
#: actions/usersalmon.php:71 classes/Ostatus_profile.php:513
msgid "Cannot handle that kind of post."
msgstr ""

#. TRANS: Client exception.
#: actions/usersalmon.php:95
msgid "Not to anyone in reply to anything."
msgstr ""

#. TRANS: Client exception.
#: actions/usersalmon.php:138 actions/usersalmon.php:170
msgid "Not a person object."
msgstr ""

#. TRANS: Client exception.
#: actions/usersalmon.php:145
msgid "Unidentified profile being listed."
msgstr ""

#. TRANS: Client exception.
#: actions/usersalmon.php:150
msgid "This user is not the one being listed."
msgstr ""

#. TRANS: Client exception.
#: actions/usersalmon.php:160
msgid "The listing could not be saved."
msgstr ""

#. TRANS: Client exception.
#: actions/usersalmon.php:177
msgid "Unidentified profile being unlisted."
msgstr ""

#. TRANS: Client exception.
#: actions/usersalmon.php:182
msgid "This user is not the one being unlisted."
msgstr ""

#. TRANS: Client exception.
#: actions/usersalmon.php:193
msgid "The listing could not be deleted."
msgstr ""

#. TRANS: Client exception.
#: actions/usersalmon.php:214
msgid "Cannot handle that kind of object for liking/faving."
msgstr ""

#. TRANS: Client exception. %s is an object ID.
#: actions/usersalmon.php:221
#, php-format
msgid "Notice with ID %s unknown."
msgstr ""

#. TRANS: Client exception. %1$s is a notice ID, %2$s is a user ID.
#: actions/usersalmon.php:226
#, php-format
msgid "Notice with ID %1$s not posted by %2$s."
msgstr ""

#. TRANS: Field label.
#: actions/ostatuspeopletag.php:74
msgid "Subscribe to list"
msgstr ""

#. TRANS: Field title.
#: actions/ostatuspeopletag.php:77
msgid "Address of the OStatus list, like http://example.net/user/all/tag."
msgstr ""

#. TRANS: Error text displayed when trying to subscribe to a list already a subscriber to.
#: actions/ostatuspeopletag.php:102
msgid "You are already subscribed to this list."
msgstr ""

#. TRANS: OStatus remote group subscription dialog error.
#. TRANS: OStatus remote subscription dialog error.
#: actions/ostatuspeopletag.php:144 actions/ostatussub.php:318
msgid "Already subscribed!"
msgstr ""

#. TRANS: Page title for OStatus remote list subscription form
#: actions/ostatuspeopletag.php:165
msgid "Confirm subscription to remote list"
msgstr ""

#. TRANS: Instructions for OStatus list subscription form.
#: actions/ostatuspeopletag.php:177
msgid ""
"You can subscribe to lists from other supported sites. Paste the list's URI "
"below:"
msgstr ""

#. TRANS: Client error.
#: actions/groupsalmon.php:54
msgid "Cannot accept remote posts for a remote group."
msgstr ""

#. TRANS: Client error.
#: actions/groupsalmon.php:125
msgid "Groups cannot join groups."
msgstr ""

#. TRANS: Client error displayed when trying to join a group the user is blocked from by a group admin.
#: actions/groupsalmon.php:138
msgid "You have been blocked from that group by the admin."
msgstr ""

#. TRANS: Server error. %1$s is a profile URI, %2$s is a group nickname.
#: actions/groupsalmon.php:145
#, php-format
msgid "Could not join remote user %1$s to group %2$s."
msgstr ""

#. TRANS: Client error displayed when trying to have a group join another group.
#: actions/groupsalmon.php:157
msgid "Groups cannot be members of groups"
msgstr ""

#. TRANS: Server error. %1$s is a profile URI, %2$s is a group nickname.
#: actions/groupsalmon.php:166
#, php-format
msgid "Could not remove remote user %1$s from group %2$s."
msgstr ""

#. TRANS: Field label for a field that takes an OStatus user address.
#: actions/ostatussub.php:94
msgid "Subscribe to"
msgstr ""

#. TRANS: Tooltip for field label "Subscribe to".
#: actions/ostatussub.php:97
msgid ""
"OStatus user's address, like nickname@example.com or http://example.net/"
"nickname."
msgstr ""

#. TRANS: Button text.
#: actions/ostatussub.php:136
msgid "Join"
msgstr ""

#. TRANS: Tooltip for button "Join".
#: actions/ostatussub.php:138
msgctxt "BUTTON"
msgid "Join this group"
msgstr ""

#. TRANS: Button text.
#: actions/ostatussub.php:141
msgctxt "BUTTON"
msgid "Confirm"
msgstr ""

#. TRANS: Tooltip for button "Confirm".
#: actions/ostatussub.php:143
msgid "Subscribe to this user"
msgstr ""

#. TRANS: Extra paragraph in remote profile view when already subscribed.
#: actions/ostatussub.php:164
msgid "You are already subscribed to this user."
msgstr ""

#. TRANS: Error text.
#: actions/ostatussub.php:265 actions/ostatussub.php:269
#: actions/ostatussub.php:273 actions/ostatussub.php:277
#: actions/ostatussub.php:281
msgid ""
"Sorry, we could not reach that feed. Please try that OStatus address again "
"later."
msgstr ""

#. TRANS: OStatus remote subscription dialog error.
#: actions/ostatussub.php:323
msgid "Remote subscription failed!"
msgstr ""

#. TRANS: Form title.
#: actions/ostatussub.php:365
msgid "Subscribe to user"
msgstr ""

#. TRANS: Page title for OStatus remote subscription form.
#: actions/ostatussub.php:385
msgid "Confirm"
msgstr ""

#. TRANS: Instructions.
#: actions/ostatussub.php:397
msgid ""
"You can subscribe to users from other supported sites. Paste their address "
"or profile URI below:"
msgstr ""

#. TRANS: Server exception.
#: classes/FeedSub.php:219
msgid "Attempting to start PuSH subscription for feed with no hub."
msgstr ""

#. TRANS: Server exception.
#: classes/FeedSub.php:255
msgid "Attempting to end PuSH subscription for feed with no hub."
msgstr ""

#. TRANS: Client exception. %s is a HTTP status code.
#: classes/HubSub.php:163
#, php-format
msgid "Hub subscriber verification returned HTTP %s."
msgstr ""

#. TRANS: Exception. %1$s is a response status code, %2$s is the body of the response.
#: classes/HubSub.php:269
#, php-format
msgid "Callback returned status: %1$s. Body: %2$s"
msgstr ""

#. TRANS: Server exception. %s is a URI
#: classes/Ostatus_profile.php:190 classes/Ostatus_profile.php:208
#, php-format
msgid "Invalid ostatus_profile state: Two or more IDs set for %s."
msgstr ""

#. TRANS: Server exception. %s is a URI
#: classes/Ostatus_profile.php:193 classes/Ostatus_profile.php:211
#, php-format
msgid "Invalid ostatus_profile state: All IDs empty for %s."
msgstr ""

#. TRANS: Server exception.
#: classes/Ostatus_profile.php:402
msgid ""
"Invalid type passed to Ostatus_profile::notify. It must be XML string or "
"Activity entry."
msgstr ""

#. TRANS: Exception.
#: classes/Ostatus_profile.php:435
msgid "Unknown feed format."
msgstr ""

#. TRANS: Exception.
#: classes/Ostatus_profile.php:459
msgid "RSS feed without a channel."
msgstr ""

#. TRANS: Client exception thrown when trying to share multiple activities at once.
#: classes/Ostatus_profile.php:551
msgid "Can only handle share activities with exactly one object."
msgstr ""

#. TRANS: Client exception thrown when trying to share a non-activity object.
#: classes/Ostatus_profile.php:558
msgid "Can only handle shared activities."
msgstr ""

#: classes/Ostatus_profile.php:569
msgid "Shared activity does not have an id"
msgstr ""

#. TRANS: Client exception thrown when saving an activity share fails.
#. TRANS: %s is a share ID.
#: classes/Ostatus_profile.php:587
#, php-format
msgid "Failed to save activity %s."
msgstr ""

#. TRANS: Client exception. %s is a source URI.
#: classes/Ostatus_profile.php:619 classes/Ostatus_profile.php:786
#, php-format
msgid "No content for notice %s."
msgstr ""

#. TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
#. TRANS: this will usually be replaced with localised text from StatusNet core messages.
#: classes/Ostatus_profile.php:654 classes/Ostatus_profile.php:821
msgid "Show more"
msgstr ""

#. TRANS: Exception. %s is a profile URL.
#: classes/Ostatus_profile.php:1018
#, php-format
msgid "Could not reach profile page %s."
msgstr ""

#. TRANS: Exception. %s is a URL.
#: classes/Ostatus_profile.php:1076 scripts/update_ostatus_profiles.php:123
#, php-format
msgid "Could not find a feed URL for profile page %s."
msgstr ""

#. TRANS: Feed sub exception.
#: classes/Ostatus_profile.php:1183
msgid "Cannot find enough profile information to make a feed."
msgstr ""

#. TRANS: Server exception. %s is a URL.
#: classes/Ostatus_profile.php:1255
#, php-format
msgid "Invalid avatar URL %s."
msgstr ""

#. TRANS: Server exception.
#: classes/Ostatus_profile.php:1464
msgid "No author ID URI found."
msgstr ""

#. TRANS: Exception.
#: classes/Ostatus_profile.php:1490
msgid "No profile URI."
msgstr ""

#. TRANS: Exception.
#: classes/Ostatus_profile.php:1496
msgid "Local user cannot be referenced as remote."
msgstr ""

#. TRANS: Exception.
#: classes/Ostatus_profile.php:1501
msgid "Local group cannot be referenced as remote."
msgstr ""

#. TRANS: Exception.
#: classes/Ostatus_profile.php:1509
msgid "Local list cannot be referenced as remote."
msgstr ""

#. TRANS: Server exception.
#: classes/Ostatus_profile.php:1564 classes/Ostatus_profile.php:1603
msgid "Cannot save local profile."
msgstr ""

#. TRANS: Server exception.
#: classes/Ostatus_profile.php:1616
msgid "Cannot save local list."
msgstr ""

#. TRANS: Server exception.
#: classes/Ostatus_profile.php:1624
msgid "Cannot save OStatus profile."
msgstr ""

#. TRANS: Exception.
#: classes/Ostatus_profile.php:1937 classes/Ostatus_profile.php:1964
#: scripts/update_ostatus_profiles.php:148
msgid "Not a valid webfinger address."
msgstr ""

#. TRANS: Exception. %s is a webfinger address.
#: classes/Ostatus_profile.php:2046
#, php-format
msgid "Could not save profile for \"%s\"."
msgstr ""

#. TRANS: Exception. %s is a webfinger address.
#: classes/Ostatus_profile.php:2066
#, php-format
msgid "Could not save OStatus profile for \"%s\"."
msgstr ""

#. TRANS: Exception. %s is a webfinger address.
#: classes/Ostatus_profile.php:2074 scripts/update_ostatus_profiles.php:203
#, php-format
msgid "Could not find a valid profile for \"%s\"."
msgstr ""

#. TRANS: Server exception.
#: classes/Ostatus_profile.php:2117
msgid "Could not store HTML content of long post as file."
msgstr ""

#. TRANS: Server exception.
#. TRANS: %1$s is a protocol, %2$s is a URI.
#: classes/Ostatus_profile.php:2151
#, php-format
msgid "Unrecognized URI protocol for profile: %1$s (%2$s)."
msgstr ""

#. TRANS: Server exception. %s is a URI.
#: classes/Ostatus_profile.php:2158
#, php-format
msgid "No URI protocol for profile: %s."
msgstr ""

#. TRANS: Exception.
#: lib/magicenvelope.php:116
msgid "Unable to locate signer public key."
msgstr ""

#. TRANS: Exception when public key was not properly formatted.
#: lib/magicenvelope.php:135
msgid "Incorrectly formatted public key element."
msgstr ""

#: lib/magicenvelope.php:217
msgid "Salmon post must be an Atom entry."
msgstr ""

#. TRANS: Client error. Do not translate "application/magic-envelope+xml".
#: lib/salmonaction.php:46
msgid "Salmon requires \"application/magic-envelope+xml\"."
msgstr ""

#. TRANS: Exception.
#: lib/salmonaction.php:59
msgid "Received a salmon slap from unidentified actor."
msgstr ""

#. TRANS: Client error.
#: lib/salmonaction.php:72
msgid "Salmon signature verification failed."
msgstr ""

#. TRANS: Client exception.
#: lib/salmonaction.php:121
msgid "Unrecognized activity type."
msgstr ""

#. TRANS: Client exception.
#: lib/salmonaction.php:137
msgid "This target does not understand posts."
msgstr ""

#. TRANS: Client exception.
#: lib/salmonaction.php:143
msgid "This target does not understand follows."
msgstr ""

#. TRANS: Client exception.
#: lib/salmonaction.php:149
msgid "This target does not understand unfollows."
msgstr ""

#. TRANS: Client exception.
#: lib/salmonaction.php:155
msgid "This target does not understand share events."
msgstr ""

#. TRANS: Client exception.
#: lib/salmonaction.php:161
msgid "This target does not understand joins."
msgstr ""

#. TRANS: Client exception.
#: lib/salmonaction.php:167
msgid "This target does not understand leave events."
msgstr ""

#. TRANS: Client exception.
#: lib/salmonaction.php:173
msgid "This target does not understand list events."
msgstr ""

#. TRANS: Client exception.
#: lib/salmonaction.php:179
msgid "This target does not understand unlist events."
msgstr ""

#. TRANS: String in the gettext speed test script. Unimportant.
#: tests/gettext-speedtest.php:58
msgid "Feeds"
msgstr ""