Update PuSH callback URL if remote side switched to HTTPS

See the comment in the source on why we're not following Location headers...
This commit is contained in:
Mikael Nordfeldth 2016-01-11 19:55:02 +01:00
parent f24cdf4a80
commit bd6efa0e45

View File

@ -40,7 +40,7 @@ class HubSub extends Managed_DataObject
public $created;
public $modified;
protected static function hashkey($topic, $callback)
static function hashkey($topic, $callback)
{
return sha1($topic . '|' . $callback);
}
@ -120,6 +120,11 @@ class HubSub extends Managed_DataObject
$qm->enqueue($data, 'hubconf');
}
public function getTopic()
{
return $this->topic;
}
/**
* Send a verification ping to subscriber, and if confirmed apply the changes.
* This may create, update, or delete the database record.
@ -134,7 +139,7 @@ class HubSub extends Managed_DataObject
$challenge = common_random_hexstr(32);
$params = array('hub.mode' => $mode,
'hub.topic' => $this->topic,
'hub.topic' => $this->getTopic(),
'hub.challenge' => $challenge);
if ($mode == 'subscribe') {
$params['hub.lease_seconds'] = $this->lease;
@ -157,13 +162,13 @@ class HubSub extends Managed_DataObject
$status = $response->getStatus();
if ($status >= 200 && $status < 300) {
common_log(LOG_INFO, "Verified {$mode} of {$this->callback}:{$this->topic}");
common_log(LOG_INFO, "Verified {$mode} of {$this->callback}:{$this->getTopic()}");
} else {
// TRANS: Client exception. %s is a HTTP status code.
throw new ClientException(sprintf(_m('Hub subscriber verification returned HTTP %s.'),$status));
}
$old = HubSub::getByHashkey($this->topic, $this->callback);
$old = HubSub::getByHashkey($this->getTopic(), $this->callback);
if ($mode == 'subscribe') {
if ($old instanceof HubSub) {
$this->update($old);
@ -185,7 +190,7 @@ class HubSub extends Managed_DataObject
*/
function insert()
{
$this->hashkey = self::hashkey($this->topic, $this->callback);
$this->hashkey = self::hashkey($this->getTopic(), $this->callback);
$this->created = common_sql_now();
$this->modified = common_sql_now();
return parent::insert();
@ -208,11 +213,11 @@ class HubSub extends Managed_DataObject
// destroy the result data for the parent query.
// @fixme use clone() again when it's safe to copy an
// individual item from a multi-item query again.
$sub = HubSub::getByHashkey($this->topic, $this->callback);
$sub = HubSub::getByHashkey($this->getTopic(), $this->callback);
$data = array('sub' => $sub,
'atom' => $atom,
'retries' => $retries);
common_log(LOG_INFO, "Queuing PuSH: $this->topic to $this->callback");
common_log(LOG_INFO, "Queuing PuSH: {$this->getTopic()} to {$this->callback}");
$qm = QueueManager::get();
$qm->enqueue($data, 'hubout');
}
@ -229,10 +234,9 @@ class HubSub extends Managed_DataObject
function bulkDistribute($atom, $pushCallbacks)
{
$data = array('atom' => $atom,
'topic' => $this->topic,
'topic' => $this->getTopic(),
'pushCallbacks' => $pushCallbacks);
common_log(LOG_INFO, "Queuing PuSH batch: $this->topic to " .
count($pushCallbacks) . " sites");
common_log(LOG_INFO, "Queuing PuSH batch: {$this->getTopic()} to ".count($pushCallbacks)." sites");
$qm = QueueManager::get();
$qm->enqueue($data, 'hubprep');
}
@ -256,18 +260,58 @@ class HubSub extends Managed_DataObject
} else {
$hmac = '(none)';
}
common_log(LOG_INFO, "About to push feed to $this->callback for $this->topic, HMAC $hmac");
common_log(LOG_INFO, "About to push feed to $this->callback for {$this->getTopic()}, HMAC $hmac");
$request = new HTTPClient();
$request->setBody($atom);
try {
$response = $request->post($this->callback, $headers);
if ($response->isOk()) {
return true;
} else {
}
} catch (Exception $e) {
$response = null;
common_debug('PuSH callback to '._ve($this->callback).' for '._ve($this->getTopic()).' failed with exception: '._ve($e->getMessage()));
}
// XXX: DO NOT trust a Location header here, _especially_ from 'http' protocols,
// but not 'https' either at least if we don't do proper CA verification. Trust that
// the most common change here is simply switching 'http' to 'https' and we will
// solve 99% of all of these issues for now. There should be a proper mechanism
// if we want to change the callback URLs, preferrably just manual resubscriptions
// from the remote side, combined with implemented PuSH subscription timeouts.
// We failed the PuSH, but it might be that the remote site has changed their configuration to HTTPS
if ('http' === parse_url($this->callback, PHP_URL_SCHEME)) {
// Test if the feed callback for this node has migrated to HTTPS
$httpscallback = preg_replace('/^http/', 'https', $this->callback, 1);
if ($httpscallback === $this->callback) {
throw new ServerException('Trying to preg_replace http to https on '._ve($this->callback).' failed and resulted in an identical string: '._ve($httpscallback).'.');
}
common_debug('PuSH callback to '._ve($this->callback).' for '._ve($this->getTopic()).' testing with HTTPS callback: '._ve($httpscallback));
$response = $request->post($httpscallback, $headers);
if ($response->isOk()) {
$orig = clone($this);
$this->callback = $httpscallback;
$this->hashkey = self::hashkey($this->getTopic(), $this->callback);
common_debug('HubSub DEBUG, from '._ve($orig).' to '._ve($this));
$this->updateWithKeys($orig, 'hashkey');
return true;
}
}
// FIXME: Add 'failed' incremental count for this callback.
if (is_null($response)) {
// This means we got a lower-than-HTTP level error, like domain not found or maybe connection refused
// This should be using a more distinguishable exception class, but for now this will do.
throw new Exception(sprintf(_m('HTTP request failed without response to URL: %s'), var_export($target, true)));
}
// TRANS: Exception. %1$s is a response status code, %2$s is the body of the response.
throw new Exception(sprintf(_m('Callback returned status: %1$s. Body: %2$s'),
$response->getStatus(),trim($response->getBody())));
}
}
}