From a4a37d6fc9738c698c99cc8ee7ccaa454f6cc8e3 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 14 Oct 2015 15:28:38 -0500 Subject: [PATCH 01/10] More robust pingback link detection --- plugins/Linkback/LinkbackPlugin.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Linkback/LinkbackPlugin.php b/plugins/Linkback/LinkbackPlugin.php index a710abd7bf..37c3ef8768 100644 --- a/plugins/Linkback/LinkbackPlugin.php +++ b/plugins/Linkback/LinkbackPlugin.php @@ -100,9 +100,9 @@ class LinkbackPlugin extends Plugin if (array_key_exists('X-Pingback', $result->headers)) { $pb = $result->headers['X-Pingback']; - } else if (preg_match('//', - $result->body, - $match)) { + + } else if(preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="[^" ]* ?pingback ?[^" ]*"[ ]*\/?>/i', $result->body, $match) + || preg_match('/<(?:link|a)[ ]+rel="[^" ]* ?pingback ?[^" ]*"[ ]+href="([^"]+)"[ ]*\/?>/i', $result->body, $match)) { $pb = $match[1]; } From b43294ec6f81c44b156e865bf5e7bfc2b441bc7f Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 14 Oct 2015 15:32:12 -0500 Subject: [PATCH 02/10] Use the getter, not a direct access --- 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 37c3ef8768..67e9dea7c6 100644 --- a/plugins/Linkback/LinkbackPlugin.php +++ b/plugins/Linkback/LinkbackPlugin.php @@ -120,7 +120,7 @@ class LinkbackPlugin extends Plugin function pingback($url, $endpoint) { - $args = array($this->notice->uri, $url); + $args = array($this->notice->getUrl(), $url); if (!extension_loaded('xmlrpc')) { if (!dl('xmlrpc.so')) { From 63fd35dffae85ff225c4b3170f2238d838b397dd Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 14 Oct 2015 15:47:00 -0500 Subject: [PATCH 03/10] Notify replies and repeats This is especially useful for partial federation with remote accounts that are not fully OStatus-enabled but support a pingback protocol. Such accounts will still be notified of replies and repeats of their content even without OStatus support, thus adding to the federated universe. --- plugins/Linkback/LinkbackPlugin.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/Linkback/LinkbackPlugin.php b/plugins/Linkback/LinkbackPlugin.php index 67e9dea7c6..85dcf502c7 100644 --- a/plugins/Linkback/LinkbackPlugin.php +++ b/plugins/Linkback/LinkbackPlugin.php @@ -68,6 +68,14 @@ class LinkbackPlugin extends Plugin // Ignoring results common_replace_urls_callback($c, array($this, 'linkbackUrl')); + + if($notice->isRepeat()) { + $repeat = Notice::getByID($notice->repeat_of); + $this->linkbackUrl($repeat->getUrl()); + } else if(!empty($notice->reply_to)) { + $parent = $notice->getParent(); + $this->linkbackUrl($parent->getUrl()); + } } return true; } From 8edc5148d9f3aeb60ebddda4926bc3ff1dcdd555 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 14 Oct 2015 15:54:47 -0500 Subject: [PATCH 04/10] Normalize detection helpers Will make it easier to see what's happening when we add a third one. --- plugins/Linkback/LinkbackPlugin.php | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/plugins/Linkback/LinkbackPlugin.php b/plugins/Linkback/LinkbackPlugin.php index 85dcf502c7..57a788457a 100644 --- a/plugins/Linkback/LinkbackPlugin.php +++ b/plugins/Linkback/LinkbackPlugin.php @@ -103,21 +103,11 @@ class LinkbackPlugin extends Plugin return $orig; } - $pb = null; - $tb = null; - - if (array_key_exists('X-Pingback', $result->headers)) { - $pb = $result->headers['X-Pingback']; - - } else if(preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="[^" ]* ?pingback ?[^" ]*"[ ]*\/?>/i', $result->body, $match) - || preg_match('/<(?:link|a)[ ]+rel="[^" ]* ?pingback ?[^" ]*"[ ]+href="([^"]+)"[ ]*\/?>/i', $result->body, $match)) { - $pb = $match[1]; - } - + $pb = $this->getPingback($result); if (!empty($pb)) { $this->pingback($result->final_url, $pb); } else { - $tb = $this->getTrackback($result->body, $result->final_url); + $tb = $this->getTrackback($result); if (!empty($tb)) { $this->trackback($result->final_url, $tb); } @@ -126,6 +116,15 @@ class LinkbackPlugin extends Plugin return $orig; } + function getPingback($result) { + if (array_key_exists('X-Pingback', $result->headers)) { + return $result->headers['X-Pingback']; + } else if(preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="[^" ]* ?pingback ?[^" ]*"[ ]*\/?>/i', $result->body, $match) + || preg_match('/<(?:link|a)[ ]+rel="[^" ]* ?pingback ?[^" ]*"[ ]+href="([^"]+)"[ ]*\/?>/i', $result->body, $match)) { + return $match[1]; + } + } + function pingback($url, $endpoint) { $args = array($this->notice->getUrl(), $url); @@ -161,8 +160,11 @@ class LinkbackPlugin extends Plugin // Largely cadged from trackback_cls.php by // Ran Aroussi , GPL2 or any later version // http://phptrackback.sourceforge.net/ - function getTrackback($text, $url) + function getTrackback($result) { + $text = $result->body; + $url = $result->final_url; + if (preg_match_all('/()/sm', $text, $match, PREG_SET_ORDER)) { for ($i = 0; $i < count($match); $i++) { if (preg_match('|dc:identifier="' . preg_quote($url) . '"|ms', $match[$i][1])) { From e4892d21b10224b00d99a78420331a747c9d2b39 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 14 Oct 2015 16:13:25 -0500 Subject: [PATCH 05/10] Note that we should handle relative URIs --- plugins/Linkback/LinkbackPlugin.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/Linkback/LinkbackPlugin.php b/plugins/Linkback/LinkbackPlugin.php index 57a788457a..84215046fa 100644 --- a/plugins/Linkback/LinkbackPlugin.php +++ b/plugins/Linkback/LinkbackPlugin.php @@ -106,6 +106,8 @@ class LinkbackPlugin extends Plugin $pb = $this->getPingback($result); if (!empty($pb)) { $this->pingback($result->final_url, $pb); + // XXX: Should handle relative-URI resolution in these detections + } else { $tb = $this->getTrackback($result); if (!empty($tb)) { From 3b1792c8b5066a28c5d64b903c63419d3e238ff1 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 14 Oct 2015 16:13:58 -0500 Subject: [PATCH 06/10] Add webmention support --- plugins/Linkback/LinkbackPlugin.php | 64 ++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/plugins/Linkback/LinkbackPlugin.php b/plugins/Linkback/LinkbackPlugin.php index 84215046fa..045fc2f4f7 100644 --- a/plugins/Linkback/LinkbackPlugin.php +++ b/plugins/Linkback/LinkbackPlugin.php @@ -103,21 +103,73 @@ class LinkbackPlugin extends Plugin return $orig; } - $pb = $this->getPingback($result); - if (!empty($pb)) { - $this->pingback($result->final_url, $pb); // XXX: Should handle relative-URI resolution in these detections + $wm = $this->getWebmention($result); + if(!empty($wm)) { + $this->webmention($result->final_url, $wm); } else { - $tb = $this->getTrackback($result); - if (!empty($tb)) { - $this->trackback($result->final_url, $tb); + $pb = $this->getPingback($result); + if (!empty($pb)) { + $this->pingback($result->final_url, $pb); + } else { + $tb = $this->getTrackback($result); + if (!empty($tb)) { + $this->trackback($result->final_url, $tb); + } } } return $orig; } + // Based on https://github.com/indieweb/mention-client-php + // which is licensed Apache 2.0 + function getWebmention($result) { + // XXX: the fetcher only gives back one of each header, so this may fail on multiple Link headers + if(preg_match('~<((?:https?://)?[^>]+)>; rel="webmention"~', $result->headers['Link'], $match)) { + return $match[1]; + } elseif(preg_match('~<((?:https?://)?[^>]+)>; rel="http://webmention.org/?"~', $result->headers['Link'], $match)) { + return $match[1]; + } + + if(preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="[^" ]* ?webmention ?[^" ]*"[ ]*\/?>/i', $result->body, $match) + || preg_match('/<(?:link|a)[ ]+rel="[^" ]* ?webmention ?[^" ]*"[ ]+href="([^"]+)"[ ]*\/?>/i', $result->body, $match)) { + return $match[1]; + } elseif(preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="http:\/\/webmention\.org\/?"[ ]*\/?>/i', $result->body, $match) + || preg_match('/<(?:link|a)[ ]+rel="http:\/\/webmention\.org\/?"[ ]+href="([^"]+)"[ ]*\/?>/i', $result->body, $match)) { + return $match[1]; + } + } + + function webmention($url, $endpoint) { + $source = $this->notice->getUrl(); + + $payload = array( + 'source' => $source, + 'target' => $url + ); + + $request = HTTPClient::start(); + try { + $response = $request->post($endpoint, + array( + 'Content-type: application/x-www-form-urlencoded', + 'Accept: application/json' + ), + $payload + ); + + if(!in_array($response->getStatus(), array(200,202))) { + common_log(LOG_WARNING, + "Webmention request failed for '$url' ($endpoint)"); + } + } catch (HTTP_Request2_Exception $e) { + common_log(LOG_WARNING, + "Webmention request failed for '$url' ($endpoint)"); + } + } + function getPingback($result) { if (array_key_exists('X-Pingback', $result->headers)) { return $result->headers['X-Pingback']; From d9d74ca96c835049fd57f9dc5a58e01e268756de Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 17 Oct 2015 13:38:13 +0000 Subject: [PATCH 07/10] Send URL we publish Webmention and Pingback both need the exact URL we are going to claim to link to to be present in our HTML source, so send them our actual original link. Webmention clients are supposed to resolve this link. Pingback clients may still fail on shortened links. --- plugins/Linkback/LinkbackPlugin.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/Linkback/LinkbackPlugin.php b/plugins/Linkback/LinkbackPlugin.php index 045fc2f4f7..9602fd7fce 100644 --- a/plugins/Linkback/LinkbackPlugin.php +++ b/plugins/Linkback/LinkbackPlugin.php @@ -107,11 +107,15 @@ class LinkbackPlugin extends Plugin $wm = $this->getWebmention($result); if(!empty($wm)) { - $this->webmention($result->final_url, $wm); + // It is the webmention receiver's job to resolve source + // Ref: https://github.com/converspace/webmention/issues/43 + $this->webmention($url, $wm); } else { $pb = $this->getPingback($result); if (!empty($pb)) { - $this->pingback($result->final_url, $pb); + // Pingback still looks for exact URL in our source, so we + // must send what we have + $this->pingback($url, $pb); } else { $tb = $this->getTrackback($result); if (!empty($tb)) { From c7e08195e4f6c0d3e431f807342234e0a5a7ba1f Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 17 Oct 2015 13:39:46 +0000 Subject: [PATCH 08/10] Fix Pingback This code was using the HTTPRequest helpers wrong. This commit sets the body directly instead of jamming the XML in as a POST param. --- plugins/Linkback/LinkbackPlugin.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Linkback/LinkbackPlugin.php b/plugins/Linkback/LinkbackPlugin.php index 9602fd7fce..4e49de670f 100644 --- a/plugins/Linkback/LinkbackPlugin.php +++ b/plugins/Linkback/LinkbackPlugin.php @@ -196,9 +196,10 @@ class LinkbackPlugin extends Plugin $request = HTTPClient::start(); try { + $request->setBody(xmlrpc_encode_request('pingback.ping', $args)); $response = $request->post($endpoint, array('Content-Type: text/xml'), - xmlrpc_encode_request('pingback.ping', $args)); + false); $response = xmlrpc_decode($response->getBody()); if (xmlrpc_is_fault($response)) { common_log(LOG_WARNING, From 677f0ac479d9c450257822e41f01d33e70f95503 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sun, 18 Oct 2015 21:28:55 +0000 Subject: [PATCH 09/10] Allow users to opt out of sending linkbacks --- plugins/Linkback/LinkbackPlugin.php | 30 +++++- plugins/Linkback/actions/linkbacksettings.php | 91 +++++++++++++++++++ 2 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 plugins/Linkback/actions/linkbacksettings.php diff --git a/plugins/Linkback/LinkbackPlugin.php b/plugins/Linkback/LinkbackPlugin.php index 4e49de670f..4af67baba4 100644 --- a/plugins/Linkback/LinkbackPlugin.php +++ b/plugins/Linkback/LinkbackPlugin.php @@ -65,9 +65,14 @@ class LinkbackPlugin extends Plugin // notice content $c = $notice->content; $this->notice = $notice; - // Ignoring results - common_replace_urls_callback($c, - array($this, 'linkbackUrl')); + + if(!$notice->getProfile()-> + getPref("linkbackplugin", "disable_linkbacks") + ) { + // Ignoring results + common_replace_urls_callback($c, + array($this, 'linkbackUrl')); + } if($notice->isRepeat()) { $repeat = Notice::getByID($notice->repeat_of); @@ -315,4 +320,23 @@ class LinkbackPlugin extends Plugin 'or Trackback protocols.')); return true; } + + public function onStartInitializeRouter(URLMapper $m) + { + $m->connect('settings/linkback', array('action' => 'linkbacksettings')); + return true; + } + + function onEndAccountSettingsNav($action) + { + $action_name = $action->trimmed('action'); + + $action->menuItem(common_local_url('linkbacksettings'), + // TRANS: OpenID plugin menu item on user settings page. + _m('MENU', 'Send Linkbacks'), + // TRANS: OpenID plugin tooltip for user settings menu item. + _m('Opt-out of sending linkbacks.'), + $action_name === 'linkbacksettings'); + return true; + } } diff --git a/plugins/Linkback/actions/linkbacksettings.php b/plugins/Linkback/actions/linkbacksettings.php new file mode 100644 index 0000000000..261e2979dc --- /dev/null +++ b/plugins/Linkback/actions/linkbacksettings.php @@ -0,0 +1,91 @@ +. + * + * @category Settings + * @package StatusNet + * @author Stephen Paul Weber + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +/** + * Settings for Linkback + * + * Lets users opt out of sending linkbacks + * + * @category Settings + * @author Stephen Paul Weber + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + */ +class LinkbacksettingsAction extends SettingsAction +{ + /** + * Title of the page + * + * @return string Page title + */ + function title() + { + // TRANS: Title of Linkback settings page for a user. + return _m('TITLE','Linkback settings'); + } + + /** + * Instructions for use + * + * @return string Instructions for use + */ + function getInstructions() + { + // TRANS: Form instructions for Linkback settings. + return _m('Linkbacks inform post authors when you link to them. ' . + 'You can disable this feature here.'); + } + + function showContent() + { + $this->elementStart('form', array('method' => 'post', + 'class' => 'form_settings', + 'action' => + common_local_url('linkbacksettings'))); + $this->hidden('token', common_session_token()); + + $this->elementStart('fieldset'); + $this->element('legend', null, _m('LEGEND','Preferences')); + $this->checkbox('disable_linkbacks', "Opt out of sending linkbacks for URLs you post", $this->scoped->getPref("linkbackplugin", "disable_linkbacks")); + // TRANS: Button text to save OpenID prefs + $this->submit('settings_linkback_prefs_save', _m('BUTTON','Save'), 'submit', 'save_prefs'); + $this->elementEnd('fieldset'); + + $this->elementEnd('form'); + } + + /** + * Handle a POST request + * + * @return void + */ + protected function doPost() + { + $x = $this->scoped->setPref("linkbackplugin", "disable_linkbacks", $this->boolean('disable_linkbacks')); + + return _m('Linkback preferences saved.'); + } +} From dc36621dc225c60251f301f98744d1364cef0972 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sun, 25 Oct 2015 17:22:15 +0000 Subject: [PATCH 10/10] Linkback to mentioned profiles --- plugins/Linkback/LinkbackPlugin.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/Linkback/LinkbackPlugin.php b/plugins/Linkback/LinkbackPlugin.php index 4af67baba4..e3519dac9e 100644 --- a/plugins/Linkback/LinkbackPlugin.php +++ b/plugins/Linkback/LinkbackPlugin.php @@ -81,6 +81,11 @@ class LinkbackPlugin extends Plugin $parent = $notice->getParent(); $this->linkbackUrl($parent->getUrl()); } + + $replyProfiles = Profile::multiGet('id', $notice->getReplies()); + foreach($replyProfiles->fetchAll('profileurl') as $profileurl) { + $this->linkbackUrl($profileurl); + } } return true; }