X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=plugins%2FOStatus%2Factions%2Fpushhub.php;h=13ec09d52871634b3bb864742bc038994923b564;hb=2729c622ff7ae8b657e6058548f863d96985b534;hp=901c18f70285f2ac302a35d4216eae7e97501b6f;hpb=82033b3773ac0fc95236716388a02bb6d2da2cab;p=quix0rs-gnu-social.git diff --git a/plugins/OStatus/actions/pushhub.php b/plugins/OStatus/actions/pushhub.php index 901c18f702..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) @@ -44,120 +43,173 @@ class PushHubAction extends Action // PHP converts '.'s in incoming var names to '_'s. // It also merges multiple values, which'll break hub.verify and hub.topic for publishing // @fixme handle multiple args - $arg = str_replace('.', '_', $arg); + $arg = str_replace('hub.', 'hub_', $arg); 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'); - 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); - $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? - - common_log(LOG_DEBUG, __METHOD__ . ': inserting'); - $ok = $sub->insert(); - - if (!$ok) { - throw new ServerException("Failed to save subscription record", 500); + $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)); } - // @fixme check errors ;) + $token = $this->arg('hub.verify_token', null); - $data = array('sub' => $sub, 'mode' => 'subscribe'); - $qm = QueueManager::get(); - $qm->enqueue($data, 'hubverify'); - - header('HTTP/1.1 202 Accepted'); - common_log(LOG_DEBUG, __METHOD__ . ': done'); + $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)); + } + + $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)); + } + } + + 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 + * @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) { - if ($sub->verify('unsubscribe')) { - $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)); } } @@ -170,7 +222,6 @@ class PushHubAction extends Action */ protected function getSub($feed, $callback) { - return HubSub::staticGet($feed, $callback); + return HubSub::getByHashkey($feed, $callback); } } -