From 47e541eaec6dc20d965e033d0af6e468b4d3f572 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 10 Jun 2016 21:00:01 +0000 Subject: [PATCH 1/9] Allow getting notice title without implying one Sometimes I just want explicit titles, and not the generated "blah posted on date" text --- classes/Notice.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index f38de37167..c130cccc26 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -248,10 +248,10 @@ class Notice extends Managed_DataObject return common_local_url('shownotice', array('notice' => $this->id), null, null, false); } - public function getTitle() + public function getTitle($imply=true) { $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: %1$s is a user name, %2$s is the notice creation date/time. $title = sprintf(_('%1$s\'s status on %2$s'), From 83e7ade714ed806d136720a55f392f270598514e Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 10 Jun 2016 21:00:48 +0000 Subject: [PATCH 2/9] When there is no useful title, class="p-name e-content" --- lib/noticelistitem.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/noticelistitem.php b/lib/noticelistitem.php index cbff03d973..b9ae0be5a0 100644 --- a/lib/noticelistitem.php +++ b/lib/noticelistitem.php @@ -179,8 +179,9 @@ class NoticeListItem extends Widget function showNoticeTitle() { if (Event::handle('StartShowNoticeTitle', array($this))) { + $nameClass = $this->notice->getTitle(false) ? 'p-name ' : ''; $this->element('a', array('href' => $this->notice->getUri(), - 'class' => 'p-name u-uid'), + 'class' => $nameClass . 'u-uid'), $this->notice->getTitle()); Event::handle('EndShowNoticeTitle', array($this)); } @@ -348,7 +349,8 @@ class NoticeListItem extends Widget function showContent() { // 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 ($this->maxchars > 0 && mb_strlen($this->notice->content) > $this->maxchars) { $this->out->text(mb_substr($this->notice->content, 0, $this->maxchars) . '[…]'); From e96d7d48f5250778b303ce5d59b312def1fa7061 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 10 Jun 2016 21:01:23 +0000 Subject: [PATCH 3/9] 400 code needs ClientException --- plugins/Linkback/actions/webmention.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/Linkback/actions/webmention.php b/plugins/Linkback/actions/webmention.php index 30bc42cea9..7a80dfef77 100644 --- a/plugins/Linkback/actions/webmention.php +++ b/plugins/Linkback/actions/webmention.php @@ -40,30 +40,30 @@ class WebmentionAction extends Action if(!$source) { echo _m('"source" is missing')."\n"; - throw new ServerException(_m('"source" is missing'), 400); + throw new ClientException(_m('"source" is missing'), 400); } if(!$target) { 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); if(!$response) { 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); if(!$notice) { 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); if(!$url) { 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"; From 4f3a03178640814fb04c0942cdbf695320fdbf3f Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 10 Jun 2016 21:01:53 +0000 Subject: [PATCH 4/9] Use strpos check properly --- plugins/Linkback/lib/util.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/Linkback/lib/util.php b/plugins/Linkback/lib/util.php index b0e99cbc94..894935b8b5 100644 --- a/plugins/Linkback/lib/util.php +++ b/plugins/Linkback/lib/util.php @@ -7,7 +7,7 @@ function linkback_lenient_target_match($body, $target) { function linkback_get_source($source, $target) { // Check if we are pinging ourselves and ignore $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); return NULL; } @@ -22,7 +22,7 @@ function linkback_get_source($source, $target) { $body = htmlspecialchars_decode($response->getBody()); // 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; } @@ -56,7 +56,7 @@ function linkback_get_target($target) { } if(!$user) { 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]); } } @@ -70,7 +70,7 @@ function linkback_get_target($target) { function linkback_is_contained_in($entry, $target) { 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']; } @@ -79,7 +79,7 @@ function linkback_is_contained_in($entry, $target) { if(isset($obj['type']) && array_intersect(array('h-cite', 'h-entry'), $obj['type']) && isset($obj['properties']) && isset($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']; } @@ -130,7 +130,7 @@ function linkback_entry_type($entry, $mf2, $target) { if($mf2['rels'] && $mf2['rels']['in-reply-to']) { 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'; } } @@ -144,7 +144,7 @@ function linkback_entry_type($entry, $mf2, $target) { ); 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]; } } @@ -152,7 +152,7 @@ function linkback_entry_type($entry, $mf2, $target) { if(isset($obj['type']) && array_intersect(array('h-cite', 'h-entry'), $obj['type']) && isset($obj['properties']) && isset($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]; } } From 624584f9df8988144f4df219db343d7b295e1444 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 10 Jun 2016 21:02:08 +0000 Subject: [PATCH 5/9] Need to strtotime before we can format the date --- plugins/Linkback/lib/util.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Linkback/lib/util.php b/plugins/Linkback/lib/util.php index 894935b8b5..f89389fc62 100644 --- a/plugins/Linkback/lib/util.php +++ b/plugins/Linkback/lib/util.php @@ -243,8 +243,8 @@ function linkback_notice($source, $notice_or_user, $entry, $author, $mf2) { if (isset($entry['published']) || isset($entry['updated'])) { $options['created'] = isset($entry['published']) - ? common_sql_date($entry['published'][0]) - : common_sql_date($entry['updated'][0]); + ? common_sql_date(strtotime($entry['published'][0])) + : common_sql_date(strtotime($entry['updated'][0])); } if (isset($entry['photo']) && common_valid_http_url($entry['photo'])) { From 6861d2f3a1e6e2bd15b884fb217b45d9b625ee10 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 10 Jun 2016 21:02:34 +0000 Subject: [PATCH 6/9] Get avatar out of entry properly --- plugins/Linkback/lib/util.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Linkback/lib/util.php b/plugins/Linkback/lib/util.php index f89389fc62..62b80ec19f 100644 --- a/plugins/Linkback/lib/util.php +++ b/plugins/Linkback/lib/util.php @@ -281,8 +281,8 @@ function linkback_notice($source, $notice_or_user, $entry, $author, $mf2) { } function linkback_profile($entry, $mf2, $response, $target) { - if(isset($entry['properties']['author']) && isset($entry['properties']['author'][0]['properties'])) { - $author = $entry['properties']['author'][0]['properties']; + if(isset($entry['author']) && isset($entry['author'][0]['properties'])) { + $author = $entry['author'][0]['properties']; } else { $author = linkback_hcard($mf2, $response->getEffectiveUrl()); } From 1e9077f5291b06dae8099dc33af9402c11e9c6aa Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 10 Jun 2016 21:02:50 +0000 Subject: [PATCH 7/9] Set avatar where available --- plugins/Linkback/lib/util.php | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/plugins/Linkback/lib/util.php b/plugins/Linkback/lib/util.php index 62b80ec19f..74dedf8840 100644 --- a/plugins/Linkback/lib/util.php +++ b/plugins/Linkback/lib/util.php @@ -280,6 +280,39 @@ function linkback_notice($source, $notice_or_user, $entry, $author, $mf2) { 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) { if(isset($entry['author']) && isset($entry['author'][0]['properties'])) { $author = $entry['author'][0]['properties']; @@ -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->created = common_sql_now(); $profile->insert(); + + if($author['photo'] && $author['photo'][0]) { + linkback_avatar($profile, $author['photo'][0]); + } } return array($profile, $author); From 274e394d8ed036206648834a817466d63781924b Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 10 Jun 2016 21:03:16 +0000 Subject: [PATCH 8/9] Pass all but two webmention.rocks tests --- plugins/Linkback/LinkbackPlugin.php | 46 ++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/plugins/Linkback/LinkbackPlugin.php b/plugins/Linkback/LinkbackPlugin.php index 701ca06fc5..da7174bdc6 100644 --- a/plugins/Linkback/LinkbackPlugin.php +++ b/plugins/Linkback/LinkbackPlugin.php @@ -101,14 +101,28 @@ class LinkbackPlugin extends Plugin 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) { common_log(LOG_DEBUG,"Attempting linkback for " . $url); $orig = $url; $url = htmlspecialchars_decode($orig); - $scheme = parse_url($url, PHP_URL_SCHEME); - if (!in_array($scheme, array('http', 'https'))) { + $base = parse_url($url); + if (!in_array($base['scheme'], array('http', 'https'))) { return $orig; } @@ -126,13 +140,17 @@ class LinkbackPlugin extends Plugin return $orig; } - // XXX: Should handle relative-URI resolution in these detections - $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 // Ref: https://github.com/converspace/webmention/issues/43 - $this->webmention($url, $wm); + $this->webmention($url, $this->unparse_url($wm)); } else { $pb = $this->getPingback($response); if (!empty($pb)) { @@ -156,26 +174,26 @@ class LinkbackPlugin extends Plugin $link = $response->getHeader('Link'); if (!is_null($link)) { // 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)) { - return $match[1]; - } elseif (preg_match('~<((?:https?://)?[^>]+)>; rel="http://webmention.org/?"~', $link, $match)) { + if (preg_match('~<([^>]+)>; rel="?(?:[^" ]* )*(?:http://webmention.org/|webmention)(?: [^" ]*)*"?~', $link, $match)) { return $match[1]; } } // FIXME: Do proper DOM traversal - if(preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="[^" ]* ?webmention ?[^" ]*"[ ]*\/?>/i', $response->getBody(), $match) - || preg_match('/<(?:link|a)[ ]+rel="[^" ]* ?webmention ?[^" ]*"[ ]+href="([^"]+)"[ ]*\/?>/i', $response->getBody(), $match)) { - return $match[1]; - } 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)) { + // Currently fails https://webmention.rocks/test/13, https://webmention.rocks/test/17 + if(preg_match('~<(?:link|a)[ ]+href="([^"]*)"[ ]+rel="(?:[^" ]* )*(?:http://webmention.org/|webmention)(?: [^" ]*)*"[ ]*/?>~i', $response->getBody(), $match) + || preg_match('~<(?:link|a)[ ]+rel="(?:[^" ]* )*(?:http://webmention.org/|webmention)(?: [^" ]*)*"[ ]+href="([^"]*)"[ ]*/?>~i', $response->getBody(), $match)) { return $match[1]; } + + return NULL; } function webmention($url, $endpoint) { $source = $this->notice->getUrl(); + common_log(LOG_DEBUG,"Attempting webmention to $endpoint for $url from $source"); + $payload = array( 'source' => $source, 'target' => $url From 97243c8a915259c6beae56959956c9e5b9dd4f8f Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 10 Jun 2016 21:13:10 +0000 Subject: [PATCH 9/9] Allow 201 as well, because spec says so --- plugins/Linkback/LinkbackPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Linkback/LinkbackPlugin.php b/plugins/Linkback/LinkbackPlugin.php index da7174bdc6..10006fa030 100644 --- a/plugins/Linkback/LinkbackPlugin.php +++ b/plugins/Linkback/LinkbackPlugin.php @@ -209,7 +209,7 @@ class LinkbackPlugin extends Plugin $payload ); - if(!in_array($response->getStatus(), array(200,202))) { + if(!in_array($response->getStatus(), array(200,201,202))) { common_log(LOG_WARNING, "Webmention request failed for '$url' ($endpoint)"); }