X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=plugins%2FOStatus%2Factions%2Fpushhub.php;h=c883647e681ef8768b58694cf9458ca49b81e894;hb=2729c622ff7ae8b657e6058548f863d96985b534;hp=19599d815f51b2ab1344f6a965976e7f28574ec5;hpb=22ff358ba8d1fd0396136e1de570d788dd0727b6;p=quix0rs-gnu-social.git diff --git a/plugins/OStatus/actions/pushhub.php b/plugins/OStatus/actions/pushhub.php index 19599d815f..c883647e68 100644 --- a/plugins/OStatus/actions/pushhub.php +++ b/plugins/OStatus/actions/pushhub.php @@ -23,20 +23,19 @@ * @maintainer Brion Vibber */ -/** - - -Things to consider... -* should we purge incomplete subscriptions that never get a verification pingback? -* when can we send subscription renewal checks? - - at next send time probably ok -* when can we handle trimming of subscriptions? - - at next send time probably ok -* should we keep a fail count? - -*/ - +if (!defined('STATUSNET')) { + exit(1); +} +/** + * Things to consider... + * should we purge incomplete subscriptions that never get a verification pingback? + * when can we send subscription renewal checks? + * - at next send time probably ok + * when can we handle trimming of subscriptions? + * - at next send time probably ok + * should we keep a fail count? + */ class PushHubAction extends Action { function arg($arg, $def=null) @@ -48,123 +47,169 @@ class PushHubAction extends Action return parent::arg($arg, $def); } - function prepare($args) + protected function prepare($args) { StatusNet::setApi(true); // reduce exception reports to aid in debugging return parent::prepare($args); } - function handle() + protected function handle() { $mode = $this->trimmed('hub.mode'); switch ($mode) { case "subscribe": - $this->subscribe(); - break; case "unsubscribe": - $this->unsubscribe(); + $this->subunsub($mode); break; case "publish": - throw new ServerException("Publishing outside feeds not supported.", 400); + // TRANS: Client exception. + throw new ClientException(_m('Publishing outside feeds not supported.'), 400); default: - throw new ServerException("Unrecognized mode '$mode'.", 400); + // TRANS: Client exception. %s is a mode. + throw new ClientException(sprintf(_m('Unrecognized mode "%s".'),$mode), 400); } } /** - * Process a PuSH feed subscription request. + * Process a request for a new or modified PuSH feed subscription. + * If asynchronous verification is requested, updates won't be saved immediately. * * HTTP return codes: * 202 Accepted - request saved and awaiting verification * 204 No Content - already subscribed - * 403 Forbidden - rejecting this (not specifically spec'd) + * 400 Bad Request - rejecting this (not specifically spec'd) */ - function subscribe() + function subunsub($mode) { - $feed = $this->argUrl('hub.topic'); $callback = $this->argUrl('hub.callback'); - $token = $this->arg('hub.verify_token', null); - common_log(LOG_DEBUG, __METHOD__ . ": checking sub'd to $feed $callback"); - if ($this->getSub($feed, $callback)) { - // Already subscribed; return 204 per spec. - header('HTTP/1.1 204 No Content'); - common_log(LOG_DEBUG, __METHOD__ . ': already subscribed'); - return; + $topic = $this->argUrl('hub.topic'); + if (!$this->recognizedFeed($topic)) { + // TRANS: Client exception. %s is a topic. + throw new ClientException(sprintf(_m('Unsupported hub.topic %s this hub only serves local user and group Atom feeds.'),$topic)); + } + + $verify = $this->arg('hub.verify'); // @fixme may be multiple + if ($verify != 'sync' && $verify != 'async') { + // TRANS: Client exception. %s is sync or async. + throw new ClientException(sprintf(_m('Invalid hub.verify "%s". It must be sync or async.'),$verify)); } - common_log(LOG_DEBUG, __METHOD__ . ': setting up'); - $sub = new HubSub(); - $sub->topic = $feed; - $sub->callback = $callback; - $sub->secret = $this->arg('hub.secret', null); - if (strlen($sub->secret) > 200) { - throw new ClientException("hub.secret must be no longer than 200 chars", 400); + $lease = $this->arg('hub.lease_seconds', null); + if ($mode == 'subscribe' && $lease != '' && !preg_match('/^\d+$/', $lease)) { + // TRANS: Client exception. %s is the invalid lease value. + throw new ClientException(sprintf(_m('Invalid hub.lease "%s". It must be empty or positive integer.'),$lease)); } - $sub->setLease(intval($this->arg('hub.lease_seconds'))); - // @fixme check for feeds we don't manage - // @fixme check the verification mode, might want a return immediately? + $token = $this->arg('hub.verify_token', null); - common_log(LOG_DEBUG, __METHOD__ . ': inserting'); - $ok = $sub->insert(); - - if (!$ok) { - throw new ServerException("Failed to save subscription record", 500); + $secret = $this->arg('hub.secret', null); + if ($secret != '' && strlen($secret) >= 200) { + // TRANS: Client exception. %s is the invalid hub secret. + throw new ClientException(sprintf(_m('Invalid hub.secret "%s". It must be under 200 bytes.'),$secret)); } - // @fixme check errors ;) + $sub = HubSub::getByHashkey($topic, $callback); + if (!$sub) { + // Creating a new one! + $sub = new HubSub(); + $sub->topic = $topic; + $sub->callback = $callback; + } + if ($mode == 'subscribe') { + if ($secret) { + $sub->secret = $secret; + } + if ($lease) { + $sub->setLease(intval($lease)); + } + } - $data = array('sub' => $sub, 'mode' => 'subscribe', 'token' => $token); - $qm = QueueManager::get(); - $qm->enqueue($data, 'hubverify'); - - header('HTTP/1.1 202 Accepted'); - common_log(LOG_DEBUG, __METHOD__ . ': done'); + if (!common_config('queue', 'enabled')) { + // Won't be able to background it. + $verify = 'sync'; + } + if ($verify == 'async') { + $sub->scheduleVerify($mode, $token); + header('HTTP/1.1 202 Accepted'); + } else { + $sub->verify($mode, $token); + header('HTTP/1.1 204 No Content'); + } } /** - * Process a PuSH feed unsubscription request. + * Check whether the given URL represents one of our canonical + * user or group Atom feeds. * - * HTTP return codes: - * 202 Accepted - request saved and awaiting verification - * 204 No Content - already subscribed - * 400 Bad Request - invalid params or rejected feed - * - * @fixme background this + * @param string $feed URL + * @return boolean true if it matches */ - function unsubscribe() + function recognizedFeed($feed) { - $feed = $this->argUrl('hub.topic'); - $callback = $this->argUrl('hub.callback'); - $sub = $this->getSub($feed, $callback); - - if ($sub) { - $token = $this->arg('hub.verify_token', null); - if ($sub->verify('unsubscribe', $token)) { - $sub->delete(); - common_log(LOG_INFO, "PuSH unsubscribed $feed for $callback"); - } else { - throw new ServerException("Failed PuSH unsubscription: verification failed! $feed for $callback"); + $matches = array(); + if (preg_match('!/(\d+)\.atom$!', $feed, $matches)) { + $id = $matches[1]; + $params = array('id' => $id, 'format' => 'atom'); + $userFeed = common_local_url('ApiTimelineUser', $params); + $groupFeed = common_local_url('ApiTimelineGroup', $params); + + if ($feed == $userFeed) { + $user = User::getKV('id', $id); + if (!$user) { + // TRANS: Client exception. %s is a feed URL. + throw new ClientException(sprintt(_m('Invalid hub.topic "%s". User does not exist.'),$feed)); + } else { + return true; + } } - } else { - throw new ServerException("Failed PuSH unsubscription: not subscribed! $feed for $callback"); + if ($feed == $groupFeed) { + $user = User_group::getKV('id', $id); + if (!$user) { + // TRANS: Client exception. %s is a feed URL. + throw new ClientException(sprintf(_m('Invalid hub.topic "%s". Group does not exist.'),$feed)); + } else { + return true; + } + } + } else if (preg_match('!/(\d+)/lists/(\d+)/statuses\.atom$!', $feed, $matches)) { + $user = $matches[1]; + $id = $matches[2]; + $params = array('user' => $user, 'id' => $id, 'format' => 'atom'); + $listFeed = common_local_url('ApiTimelineList', $params); + + if ($feed == $listFeed) { + $list = Profile_list::getKV('id', $id); + $user = User::getKV('id', $user); + if (!$list || !$user || $list->tagger != $user->id) { + // TRANS: Client exception. %s is a feed URL. + throw new ClientException(sprintf(_m('Invalid hub.topic %s; list does not exist.'),$feed)); + } else { + return true; + } + } + common_log(LOG_DEBUG, "Not a user, group or people tag feed? $feed $userFeed $groupFeed $listFeed"); } + common_log(LOG_DEBUG, "LOST $feed"); + return false; } /** * Grab and validate a URL from POST parameters. - * @throws ServerException for malformed or non-http/https URLs + * @throws ClientException for malformed or non-http/https URLs */ protected function argUrl($arg) { $url = $this->arg($arg); $params = array('domain_check' => false, // otherwise breaks my local tests :P 'allowed_schemes' => array('http', 'https')); - if (Validate::uri($url, $params)) { + $validate = new Validate; + if ($validate->uri($url, $params)) { return $url; } else { - throw new ServerException("Invalid URL passed for $arg: '$url'", 400); + // TRANS: Client exception. + // TRANS: %1$s is this argument to the method this exception occurs in, %2$s is a URL. + throw new ClientException(sprintf(_m('Invalid URL passed for %1$s: "%2$s"'),$arg,$url)); } } @@ -177,7 +222,6 @@ class PushHubAction extends Action */ protected function getSub($feed, $callback) { - return HubSub::staticGet($feed, $callback); + return HubSub::getByHashkey($feed, $callback); } } -