Merge branch 'webmention-rocks' into 'nightly'
webmention.rocks I have improved the webmention handling so that all but two of the webmention.rocks compliance tests pass now. Also improved parsing of time/authors on incoming webmentions. See merge request !128
This commit is contained in:
commit
d4295cfb25
@ -248,10 +248,10 @@ class Notice extends Managed_DataObject
|
|||||||
return common_local_url('shownotice', array('notice' => $this->id), null, null, false);
|
return common_local_url('shownotice', array('notice' => $this->id), null, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTitle()
|
public function getTitle($imply=true)
|
||||||
{
|
{
|
||||||
$title = null;
|
$title = null;
|
||||||
if (Event::handle('GetNoticeTitle', array($this, &$title))) {
|
if (Event::handle('GetNoticeTitle', array($this, &$title)) && $imply) {
|
||||||
// TRANS: Title of a notice posted without a title value.
|
// TRANS: Title of a notice posted without a title value.
|
||||||
// TRANS: %1$s is a user name, %2$s is the notice creation date/time.
|
// TRANS: %1$s is a user name, %2$s is the notice creation date/time.
|
||||||
$title = sprintf(_('%1$s\'s status on %2$s'),
|
$title = sprintf(_('%1$s\'s status on %2$s'),
|
||||||
|
@ -179,8 +179,9 @@ class NoticeListItem extends Widget
|
|||||||
function showNoticeTitle()
|
function showNoticeTitle()
|
||||||
{
|
{
|
||||||
if (Event::handle('StartShowNoticeTitle', array($this))) {
|
if (Event::handle('StartShowNoticeTitle', array($this))) {
|
||||||
|
$nameClass = $this->notice->getTitle(false) ? 'p-name ' : '';
|
||||||
$this->element('a', array('href' => $this->notice->getUri(),
|
$this->element('a', array('href' => $this->notice->getUri(),
|
||||||
'class' => 'p-name u-uid'),
|
'class' => $nameClass . 'u-uid'),
|
||||||
$this->notice->getTitle());
|
$this->notice->getTitle());
|
||||||
Event::handle('EndShowNoticeTitle', array($this));
|
Event::handle('EndShowNoticeTitle', array($this));
|
||||||
}
|
}
|
||||||
@ -348,7 +349,8 @@ class NoticeListItem extends Widget
|
|||||||
function showContent()
|
function showContent()
|
||||||
{
|
{
|
||||||
// FIXME: URL, image, video, audio
|
// FIXME: URL, image, video, audio
|
||||||
$this->out->elementStart('article', array('class' => 'e-content'));
|
$nameClass = $this->notice->getTitle(false) ? '' : 'p-name ';
|
||||||
|
$this->out->elementStart('article', array('class' => $nameClass . 'e-content'));
|
||||||
if (Event::handle('StartShowNoticeContent', array($this->notice, $this->out, $this->out->getScoped()))) {
|
if (Event::handle('StartShowNoticeContent', array($this->notice, $this->out, $this->out->getScoped()))) {
|
||||||
if ($this->maxchars > 0 && mb_strlen($this->notice->content) > $this->maxchars) {
|
if ($this->maxchars > 0 && mb_strlen($this->notice->content) > $this->maxchars) {
|
||||||
$this->out->text(mb_substr($this->notice->content, 0, $this->maxchars) . '[…]');
|
$this->out->text(mb_substr($this->notice->content, 0, $this->maxchars) . '[…]');
|
||||||
|
@ -101,14 +101,28 @@ class LinkbackPlugin extends Plugin
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unparse_url($parsed_url)
|
||||||
|
{
|
||||||
|
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
|
||||||
|
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
|
||||||
|
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
|
||||||
|
$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
|
||||||
|
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
|
||||||
|
$pass = ($user || $pass) ? "$pass@" : '';
|
||||||
|
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
|
||||||
|
$query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
|
||||||
|
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
|
||||||
|
return "$scheme$user$pass$host$port$path$query$fragment";
|
||||||
|
}
|
||||||
|
|
||||||
function linkbackUrl($url)
|
function linkbackUrl($url)
|
||||||
{
|
{
|
||||||
common_log(LOG_DEBUG,"Attempting linkback for " . $url);
|
common_log(LOG_DEBUG,"Attempting linkback for " . $url);
|
||||||
|
|
||||||
$orig = $url;
|
$orig = $url;
|
||||||
$url = htmlspecialchars_decode($orig);
|
$url = htmlspecialchars_decode($orig);
|
||||||
$scheme = parse_url($url, PHP_URL_SCHEME);
|
$base = parse_url($url);
|
||||||
if (!in_array($scheme, array('http', 'https'))) {
|
if (!in_array($base['scheme'], array('http', 'https'))) {
|
||||||
return $orig;
|
return $orig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,13 +140,17 @@ class LinkbackPlugin extends Plugin
|
|||||||
return $orig;
|
return $orig;
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: Should handle relative-URI resolution in these detections
|
|
||||||
|
|
||||||
$wm = $this->getWebmention($response);
|
$wm = $this->getWebmention($response);
|
||||||
if(!empty($wm)) {
|
if(!is_null($wm)) {
|
||||||
|
$wm = parse_url($wm);
|
||||||
|
if(!$wm) $wm = array();
|
||||||
|
if(!$wm['host']) $wm['host'] = $base['host'];
|
||||||
|
if(!$wm['scheme']) $wm['scheme'] = $base['scheme'];
|
||||||
|
if(!$wm['path']) $wm['path'] = $base['path'];
|
||||||
|
|
||||||
// It is the webmention receiver's job to resolve source
|
// It is the webmention receiver's job to resolve source
|
||||||
// Ref: https://github.com/converspace/webmention/issues/43
|
// Ref: https://github.com/converspace/webmention/issues/43
|
||||||
$this->webmention($url, $wm);
|
$this->webmention($url, $this->unparse_url($wm));
|
||||||
} else {
|
} else {
|
||||||
$pb = $this->getPingback($response);
|
$pb = $this->getPingback($response);
|
||||||
if (!empty($pb)) {
|
if (!empty($pb)) {
|
||||||
@ -156,26 +174,26 @@ class LinkbackPlugin extends Plugin
|
|||||||
$link = $response->getHeader('Link');
|
$link = $response->getHeader('Link');
|
||||||
if (!is_null($link)) {
|
if (!is_null($link)) {
|
||||||
// XXX: the fetcher gives back a comma-separated string of all Link headers, I hope the parsing works reliably
|
// XXX: the fetcher gives back a comma-separated string of all Link headers, I hope the parsing works reliably
|
||||||
if (preg_match('~<((?:https?://)?[^>]+)>; rel="webmention"~', $link, $match)) {
|
if (preg_match('~<([^>]+)>; rel="?(?:[^" ]* )*(?:http://webmention.org/|webmention)(?: [^" ]*)*"?~', $link, $match)) {
|
||||||
return $match[1];
|
|
||||||
} elseif (preg_match('~<((?:https?://)?[^>]+)>; rel="http://webmention.org/?"~', $link, $match)) {
|
|
||||||
return $match[1];
|
return $match[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Do proper DOM traversal
|
// FIXME: Do proper DOM traversal
|
||||||
if(preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="[^" ]* ?webmention ?[^" ]*"[ ]*\/?>/i', $response->getBody(), $match)
|
// Currently fails https://webmention.rocks/test/13, https://webmention.rocks/test/17
|
||||||
|| preg_match('/<(?:link|a)[ ]+rel="[^" ]* ?webmention ?[^" ]*"[ ]+href="([^"]+)"[ ]*\/?>/i', $response->getBody(), $match)) {
|
if(preg_match('~<(?:link|a)[ ]+href="([^"]*)"[ ]+rel="(?:[^" ]* )*(?:http://webmention.org/|webmention)(?: [^" ]*)*"[ ]*/?>~i', $response->getBody(), $match)
|
||||||
return $match[1];
|
|| preg_match('~<(?:link|a)[ ]+rel="(?:[^" ]* )*(?:http://webmention.org/|webmention)(?: [^" ]*)*"[ ]+href="([^"]*)"[ ]*/?>~i', $response->getBody(), $match)) {
|
||||||
} elseif (preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="http:\/\/webmention\.org\/?"[ ]*\/?>/i', $response->getBody(), $match)
|
|
||||||
|| preg_match('/<(?:link|a)[ ]+rel="http:\/\/webmention\.org\/?"[ ]+href="([^"]+)"[ ]*\/?>/i', $response->getBody(), $match)) {
|
|
||||||
return $match[1];
|
return $match[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
function webmention($url, $endpoint) {
|
function webmention($url, $endpoint) {
|
||||||
$source = $this->notice->getUrl();
|
$source = $this->notice->getUrl();
|
||||||
|
|
||||||
|
common_log(LOG_DEBUG,"Attempting webmention to $endpoint for $url from $source");
|
||||||
|
|
||||||
$payload = array(
|
$payload = array(
|
||||||
'source' => $source,
|
'source' => $source,
|
||||||
'target' => $url
|
'target' => $url
|
||||||
@ -191,7 +209,7 @@ class LinkbackPlugin extends Plugin
|
|||||||
$payload
|
$payload
|
||||||
);
|
);
|
||||||
|
|
||||||
if(!in_array($response->getStatus(), array(200,202))) {
|
if(!in_array($response->getStatus(), array(200,201,202))) {
|
||||||
common_log(LOG_WARNING,
|
common_log(LOG_WARNING,
|
||||||
"Webmention request failed for '$url' ($endpoint)");
|
"Webmention request failed for '$url' ($endpoint)");
|
||||||
}
|
}
|
||||||
|
@ -40,30 +40,30 @@ class WebmentionAction extends Action
|
|||||||
|
|
||||||
if(!$source) {
|
if(!$source) {
|
||||||
echo _m('"source" is missing')."\n";
|
echo _m('"source" is missing')."\n";
|
||||||
throw new ServerException(_m('"source" is missing'), 400);
|
throw new ClientException(_m('"source" is missing'), 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$target) {
|
if(!$target) {
|
||||||
echo _m('"target" is missing')."\n";
|
echo _m('"target" is missing')."\n";
|
||||||
throw new ServerException(_m('"target" is missing'), 400);
|
throw new ClientException(_m('"target" is missing'), 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
$response = linkback_get_source($source, $target);
|
$response = linkback_get_source($source, $target);
|
||||||
if(!$response) {
|
if(!$response) {
|
||||||
echo _m('Source does not link to target.')."\n";
|
echo _m('Source does not link to target.')."\n";
|
||||||
throw new ServerException(_m('Source does not link to target.'), 400);
|
throw new ClientException(_m('Source does not link to target.'), 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
$notice = linkback_get_target($target);
|
$notice = linkback_get_target($target);
|
||||||
if(!$notice) {
|
if(!$notice) {
|
||||||
echo _m('Target not found')."\n";
|
echo _m('Target not found')."\n";
|
||||||
throw new ServerException(_m('Target not found'), 404);
|
throw new ClientException(_m('Target not found'), 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = linkback_save($source, $target, $response, $notice);
|
$url = linkback_save($source, $target, $response, $notice);
|
||||||
if(!$url) {
|
if(!$url) {
|
||||||
echo _m('An error occured while saving.')."\n";
|
echo _m('An error occured while saving.')."\n";
|
||||||
throw new ServerException(_m('An error occured while saving.'), 500);
|
throw new ClientException(_m('An error occured while saving.'), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
echo $url."\n";
|
echo $url."\n";
|
||||||
|
@ -7,7 +7,7 @@ function linkback_lenient_target_match($body, $target) {
|
|||||||
function linkback_get_source($source, $target) {
|
function linkback_get_source($source, $target) {
|
||||||
// Check if we are pinging ourselves and ignore
|
// Check if we are pinging ourselves and ignore
|
||||||
$localprefix = common_config('site', 'server') . '/' . common_config('site', 'path');
|
$localprefix = common_config('site', 'server') . '/' . common_config('site', 'path');
|
||||||
if(linkback_lenient_target_match($source, $localprefix)) {
|
if(linkback_lenient_target_match($source, $localprefix) === 0) {
|
||||||
common_debug('Ignoring self ping from ' . $source . ' to ' . $target);
|
common_debug('Ignoring self ping from ' . $source . ' to ' . $target);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -22,7 +22,7 @@ function linkback_get_source($source, $target) {
|
|||||||
|
|
||||||
$body = htmlspecialchars_decode($response->getBody());
|
$body = htmlspecialchars_decode($response->getBody());
|
||||||
// We're slightly more lenient in our link detection than the spec requires
|
// We're slightly more lenient in our link detection than the spec requires
|
||||||
if(!linkback_lenient_target_match($body, $target)) {
|
if(linkback_lenient_target_match($body, $target) === FALSE) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ function linkback_get_target($target) {
|
|||||||
}
|
}
|
||||||
if(!$user) {
|
if(!$user) {
|
||||||
preg_match('/\/([^\/\?#]+)(?:#.*)?$/', $response->getEffectiveUrl(), $match);
|
preg_match('/\/([^\/\?#]+)(?:#.*)?$/', $response->getEffectiveUrl(), $match);
|
||||||
if(linkback_lenient_target_match(common_profile_url($match[1]), $response->getEffectiveUrl())) {
|
if(linkback_lenient_target_match(common_profile_url($match[1]), $response->getEffectiveUrl()) !== FALSE) {
|
||||||
$user = User::getKV('nickname', $match[1]);
|
$user = User::getKV('nickname', $match[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,7 +70,7 @@ function linkback_get_target($target) {
|
|||||||
|
|
||||||
function linkback_is_contained_in($entry, $target) {
|
function linkback_is_contained_in($entry, $target) {
|
||||||
foreach ((array)$entry['properties'] as $key => $values) {
|
foreach ((array)$entry['properties'] as $key => $values) {
|
||||||
if(count(array_filter($values, function($x) use ($target) { return linkback_lenient_target_match($x, $target); })) > 0) {
|
if(count(array_filter($values, function($x) use ($target) { return linkback_lenient_target_match($x, $target) !== FALSE; })) > 0) {
|
||||||
return $entry['properties'];
|
return $entry['properties'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ function linkback_is_contained_in($entry, $target) {
|
|||||||
if(isset($obj['type']) && array_intersect(array('h-cite', 'h-entry'), $obj['type']) &&
|
if(isset($obj['type']) && array_intersect(array('h-cite', 'h-entry'), $obj['type']) &&
|
||||||
isset($obj['properties']) && isset($obj['properties']['url']) &&
|
isset($obj['properties']) && isset($obj['properties']['url']) &&
|
||||||
count(array_filter($obj['properties']['url'],
|
count(array_filter($obj['properties']['url'],
|
||||||
function($x) use ($target) { return linkback_lenient_target_match($x, $target); })) > 0
|
function($x) use ($target) { return linkback_lenient_target_match($x, $target) !== FALSE; })) > 0
|
||||||
) {
|
) {
|
||||||
return $entry['properties'];
|
return $entry['properties'];
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ function linkback_entry_type($entry, $mf2, $target) {
|
|||||||
|
|
||||||
if($mf2['rels'] && $mf2['rels']['in-reply-to']) {
|
if($mf2['rels'] && $mf2['rels']['in-reply-to']) {
|
||||||
foreach($mf2['rels']['in-reply-to'] as $url) {
|
foreach($mf2['rels']['in-reply-to'] as $url) {
|
||||||
if(linkback_lenient_target_match($url, $target)) {
|
if(linkback_lenient_target_match($url, $target) !== FALSE) {
|
||||||
return 'reply';
|
return 'reply';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,7 +144,7 @@ function linkback_entry_type($entry, $mf2, $target) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
foreach((array)$entry as $key => $values) {
|
foreach((array)$entry as $key => $values) {
|
||||||
if(count(array_filter($values, function($x) use ($target) { return linkback_lenient_target_match($x, $target); })) > 0) {
|
if(count(array_filter($values, function($x) use ($target) { return linkback_lenient_target_match($x, $target) != FALSE; })) > 0) {
|
||||||
if($classes[$key]) { return $classes[$key]; }
|
if($classes[$key]) { return $classes[$key]; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +152,7 @@ function linkback_entry_type($entry, $mf2, $target) {
|
|||||||
if(isset($obj['type']) && array_intersect(array('h-cite', 'h-entry'), $obj['type']) &&
|
if(isset($obj['type']) && array_intersect(array('h-cite', 'h-entry'), $obj['type']) &&
|
||||||
isset($obj['properties']) && isset($obj['properties']['url']) &&
|
isset($obj['properties']) && isset($obj['properties']['url']) &&
|
||||||
count(array_filter($obj['properties']['url'],
|
count(array_filter($obj['properties']['url'],
|
||||||
function($x) use ($target) { return linkback_lenient_target_match($x, $target); })) > 0
|
function($x) use ($target) { return linkback_lenient_target_match($x, $target) != FALSE; })) > 0
|
||||||
) {
|
) {
|
||||||
if($classes[$key]) { return $classes[$key]; }
|
if($classes[$key]) { return $classes[$key]; }
|
||||||
}
|
}
|
||||||
@ -243,8 +243,8 @@ function linkback_notice($source, $notice_or_user, $entry, $author, $mf2) {
|
|||||||
|
|
||||||
if (isset($entry['published']) || isset($entry['updated'])) {
|
if (isset($entry['published']) || isset($entry['updated'])) {
|
||||||
$options['created'] = isset($entry['published'])
|
$options['created'] = isset($entry['published'])
|
||||||
? common_sql_date($entry['published'][0])
|
? common_sql_date(strtotime($entry['published'][0]))
|
||||||
: common_sql_date($entry['updated'][0]);
|
: common_sql_date(strtotime($entry['updated'][0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($entry['photo']) && common_valid_http_url($entry['photo'])) {
|
if (isset($entry['photo']) && common_valid_http_url($entry['photo'])) {
|
||||||
@ -280,9 +280,42 @@ function linkback_notice($source, $notice_or_user, $entry, $author, $mf2) {
|
|||||||
return array($content, $options);
|
return array($content, $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function linkback_avatar($profile, $url) {
|
||||||
|
// Ripped from OStatus plugin for now
|
||||||
|
$temp_filename = tempnam(sys_get_temp_dir(), 'linback_avatar');
|
||||||
|
try {
|
||||||
|
$imgData = HTTPClient::quickGet($url);
|
||||||
|
// Make sure it's at least an image file. ImageFile can do the rest.
|
||||||
|
if (false === getimagesizefromstring($imgData)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
file_put_contents($temp_filename, $imgData);
|
||||||
|
unset($imgData); // No need to carry this in memory.
|
||||||
|
|
||||||
|
$imagefile = new ImageFile(null, $temp_filename);
|
||||||
|
$filename = Avatar::filename($profile->id,
|
||||||
|
image_type_to_extension($imagefile->type),
|
||||||
|
null,
|
||||||
|
common_timestamp());
|
||||||
|
rename($temp_filename, Avatar::path($filename));
|
||||||
|
} catch (Exception $e) {
|
||||||
|
unlink($temp_filename);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
// @todo FIXME: Hardcoded chmod is lame, but seems to be necessary to
|
||||||
|
// keep from accidentally saving images from command-line (queues)
|
||||||
|
// that can't be read from web server, which causes hard-to-notice
|
||||||
|
// problems later on:
|
||||||
|
//
|
||||||
|
// http://status.net/open-source/issues/2663
|
||||||
|
chmod(Avatar::path($filename), 0644);
|
||||||
|
|
||||||
|
$profile->setOriginal($filename);
|
||||||
|
}
|
||||||
|
|
||||||
function linkback_profile($entry, $mf2, $response, $target) {
|
function linkback_profile($entry, $mf2, $response, $target) {
|
||||||
if(isset($entry['properties']['author']) && isset($entry['properties']['author'][0]['properties'])) {
|
if(isset($entry['author']) && isset($entry['author'][0]['properties'])) {
|
||||||
$author = $entry['properties']['author'][0]['properties'];
|
$author = $entry['author'][0]['properties'];
|
||||||
} else {
|
} else {
|
||||||
$author = linkback_hcard($mf2, $response->getEffectiveUrl());
|
$author = linkback_hcard($mf2, $response->getEffectiveUrl());
|
||||||
}
|
}
|
||||||
@ -315,6 +348,10 @@ function linkback_profile($entry, $mf2, $response, $target) {
|
|||||||
$profile->nickname = isset($author['nickname']) ? $author['nickname'][0] : str_replace(' ', '', $author['name'][0]);
|
$profile->nickname = isset($author['nickname']) ? $author['nickname'][0] : str_replace(' ', '', $author['name'][0]);
|
||||||
$profile->created = common_sql_now();
|
$profile->created = common_sql_now();
|
||||||
$profile->insert();
|
$profile->insert();
|
||||||
|
|
||||||
|
if($author['photo'] && $author['photo'][0]) {
|
||||||
|
linkback_avatar($profile, $author['photo'][0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array($profile, $author);
|
return array($profile, $author);
|
||||||
|
Loading…
Reference in New Issue
Block a user