diff --git a/rsscloud/notification-request.php b/rsscloud/notification-request.php
index 0eecf06..2690c4e 100644
--- a/rsscloud/notification-request.php
+++ b/rsscloud/notification-request.php
@@ -44,12 +44,6 @@ function rsscloud_hub_process_notification_request( ) {
$scheme = ( 443 === $port ) ? 'https://' : 'http://';
- $allow_port = function ( $ports ) use ( $port ) {
- $ports[] = $port;
- return $ports;
- };
- add_filter( 'http_allowed_safe_ports', $allow_port );
-
if ( !empty( $_POST['domain'] ) ) {
$domain = str_replace( '@', '', sanitize_text_field( wp_unslash( $_POST['domain'] ) ) );
$notify_url = $domain . ':' . $port . $path;
@@ -66,8 +60,6 @@ function rsscloud_hub_process_notification_request( ) {
$result = wp_safe_remote_post( $notify_url, array( 'method' => 'POST', 'timeout' => RSSCLOUD_HTTP_TIMEOUT, 'user-agent' => RSSCLOUD_USER_AGENT, 'port' => $port, 'body' => array( 'url' => esc_url_raw( wp_unslash( $_POST['url1'] ) ) ) ) );
}
- remove_filter( 'http_allowed_safe_ports', $allow_port );
-
if ( is_wp_error( $result ) )
rsscloud_notify_result( 'false', 'Error testing notification URL : ' . $result->get_error_message() );
diff --git a/rsscloud/readme.txt b/rsscloud/readme.txt
index 9a45ff2..fa5e697 100644
--- a/rsscloud/readme.txt
+++ b/rsscloud/readme.txt
@@ -1,7 +1,7 @@
=== Plugin Name ===
Contributors: josephscott, automattic
Tags: rss
-Requires at least: 2.8
+Requires at least: 3.6
Tested up to: 7.0.0
Stable tag: 0.5.1
@@ -15,11 +15,10 @@ Adds RSSCloud ( http://rsscloud.co/ ) capabilities to your RSS feed.
= 0.5.1 =
* Harden plugin files against direct access and escape values in the cloud element output
-* Allow notifications to subscribers using non-standard ports
+* Add the source:cloud element so newer feed readers get the https notification URL
* Allow notifications to https subscribers if port 443 is specified
* More reliable detection of failed notification requests
* Reject subscription requests with no domain when the remote address is unavailable
-* Use port 443 in cloud tag when site is https
= 0.5.0 =
* Updates to support PHP 8+
diff --git a/rsscloud/rsscloud.php b/rsscloud/rsscloud.php
index 5cae602..9c62c4c 100644
--- a/rsscloud/rsscloud.php
+++ b/rsscloud/rsscloud.php
@@ -23,6 +23,9 @@
if ( !defined( 'RSSCLOUD_HTTP_TIMEOUT' ) )
define( 'RSSCLOUD_HTTP_TIMEOUT', 3 );
+if ( !defined( 'RSSCLOUD_SOURCE_NS' ) )
+ define( 'RSSCLOUD_SOURCE_NS', 'https://source.scripting.com/' );
+
require dirname( __FILE__ ) . '/data-storage.php';
if ( !function_exists( 'rsscloud_hub_process_notification_request' ) )
@@ -52,11 +55,11 @@ function rsscloud_parse_request( $wp ) {
if ( !function_exists( 'rsscloud_notify_result' ) ) {
function rsscloud_notify_result( $success, $msg ) {
- $success = strip_tags( $success );
+ $success = wp_strip_all_tags( $success );
$success = ent2ncr( $success );
$success = esc_html( $success );
- $msg = strip_tags( $msg );
+ $msg = wp_strip_all_tags( $msg );
$msg = ent2ncr( $msg );
$msg = esc_html( $msg );
@@ -73,13 +76,11 @@ function rsscloud_add_rss_cloud_element( ) {
return;
}
- $cloud = parse_url( get_option( 'home' ) . '/?rsscloud=notify' );
+ $notify_url = get_option( 'home' ) . '/?rsscloud=notify';
- if ( isset( $cloud['port'] ) ) {
- $cloud['port'] = (int) $cloud['port'];
- } else {
- $cloud['port'] = ( isset( $cloud['scheme'] ) && 'https' === $cloud['scheme'] ) ? 443 : 80;
- }
+ $cloud = parse_url( $notify_url );
+
+ $cloud['port'] = isset( $cloud['port'] ) ? (int) $cloud['port'] : 80;
$cloud['path'] .= "?{$cloud['query']}";
@@ -89,6 +90,17 @@ function rsscloud_add_rss_cloud_element( ) {
echo " path='" . esc_attr( $cloud['path'] ) . "' registerProcedure=''";
echo " protocol='http-post' />";
echo "\n";
+
+ // Newer feed readers use source:cloud, which carries the full notification
+ // URL and so can advertise https where the cloud element above cannot.
+ // The namespace is declared on the element rather than via the rss2_ns
+ // action: declaring it here scopes it to this element, so another plugin
+ // binding the source prefix on the rss tag can neither collide with this
+ // declaration nor rebind the prefix out from under it.
+ echo "";
+ echo esc_url( $notify_url );
+ echo '';
+ echo "\n";
}
function rsscloud_generate_challenge( $length = 30 ) {
diff --git a/rsscloud/send-post-notifications.php b/rsscloud/send-post-notifications.php
index c9ba56a..63638ba 100644
--- a/rsscloud/send-post-notifications.php
+++ b/rsscloud/send-post-notifications.php
@@ -30,16 +30,8 @@ function rsscloud_send_post_notifications( $rss2_url = false ) {
if ( !empty( $url['port'] ) )
$port = $url['port'];
- $allow_port = function ( $ports ) use ( $port ) {
- $ports[] = (int) $port;
- return $ports;
- };
- add_filter( 'http_allowed_safe_ports', $allow_port );
-
$result = wp_safe_remote_post( $notify_url, array( 'method' => 'POST', 'timeout' => RSSCLOUD_HTTP_TIMEOUT, 'user-agent' => RSSCLOUD_USER_AGENT, 'port' => $port, 'body' => array( 'url' => $rss2_url ) ) );
- remove_filter( 'http_allowed_safe_ports', $allow_port );
-
do_action( 'rsscloud_send_notification' );
if ( !is_wp_error( $result ) )
diff --git a/tests/test-notification-request.php b/tests/test-notification-request.php
index 78305de..626ad3b 100644
--- a/tests/test-notification-request.php
+++ b/tests/test-notification-request.php
@@ -535,42 +535,6 @@ function ( $preempt, $args, $url ) {
$this->assertArrayHasKey( 'http://callback.example.com:9000/notify', $notify[ $this->feed_url ] );
}
- public function test_nonstandard_port_is_whitelisted_for_url_validation() {
- // No pre_http_request mock — wp_http_validate_url only runs when the
- // request isn't short-circuited, and that's where
- // http_allowed_safe_ports gets applied. Hook it at priority 999 so
- // we observe what the plugin handed to the validator.
- $observed_ports = array();
- add_filter(
- 'http_allowed_safe_ports',
- function ( $ports ) use ( &$observed_ports ) {
- $observed_ports = $ports;
- return $ports;
- },
- 999
- );
-
- // Use a TEST-NET-3 IP (RFC 5737) so wp_http_validate_url skips DNS
- // and reaches the port check where our filter runs.
- $_POST = array(
- 'url1' => $this->feed_url,
- 'port' => '4000',
- 'path' => '/feedupdated',
- 'domain' => '203.0.113.5',
- );
-
- // Request will fail at the network layer (connection refused); we
- // don't care about the outcome, only that validation was reached
- // with our port whitelisted.
- $this->call_process_notification_request();
-
- $this->assertContains(
- 4000,
- $observed_ports,
- 'Plugin should add the subscriber port to http_allowed_safe_ports before sending.'
- );
- }
-
public function test_port_443_uses_https_scheme_for_domain_based() {
add_filter(
'pre_http_request',
diff --git a/tests/test-rsscloud.php b/tests/test-rsscloud.php
index 939af50..1b0d52b 100644
--- a/tests/test-rsscloud.php
+++ b/tests/test-rsscloud.php
@@ -76,14 +76,14 @@ public function test_add_rss_cloud_element_uses_port_from_home_url() {
$this->assertStringContainsString( "port='" . $port . "'", $output );
}
- public function test_add_rss_cloud_element_defaults_port_to_443_for_https_home() {
+ public function test_add_rss_cloud_element_escapes_attribute_values() {
$this->go_to( get_feed_link( 'rss2' ) );
// Override home AFTER go_to so is_feed() still works.
add_filter(
'option_home',
function () {
- return 'https://example.com';
+ return 'http://example.com:8080/path?a=1&b=2';
}
);
@@ -91,17 +91,50 @@ function () {
rsscloud_add_rss_cloud_element();
$output = ob_get_clean();
- $this->assertStringContainsString( "port='443'", $output );
+ // With proper escaping, '&' in attribute values should become '&'.
+ $this->assertStringNotContainsString( '&b=2', $output,
+ 'Attribute values must be escaped; raw & should not appear' );
+ $this->assertStringContainsString( '&', $output,
+ 'Attribute values should use esc_attr() which converts & to &' );
}
- public function test_add_rss_cloud_element_escapes_attribute_values() {
+ public function test_add_rss_cloud_element_outputs_source_cloud_tag() {
+ $this->go_to( get_feed_link( 'rss2' ) );
+
+ ob_start();
+ rsscloud_add_rss_cloud_element();
+ $output = ob_get_clean();
+
+ $this->assertStringContainsString( 'assertStringContainsString( '', $output );
+ $this->assertStringContainsString( 'rsscloud=notify', $output );
+ }
+
+ public function test_source_cloud_declares_namespace_on_the_element() {
+ $this->go_to( get_feed_link( 'rss2' ) );
+
+ ob_start();
+ rsscloud_add_rss_cloud_element();
+ $output = ob_get_clean();
+
+ // The namespace must be declared on the element itself, not left to
+ // the rss2_ns action, so another plugin binding the source prefix on
+ // the rss tag cannot collide with it or rebind it.
+ $this->assertStringContainsString(
+ "xmlns:source='https://source.scripting.com/'",
+ $output,
+ 'source:cloud must carry its own namespace declaration'
+ );
+ }
+
+ public function test_source_cloud_uses_full_url_with_site_scheme() {
$this->go_to( get_feed_link( 'rss2' ) );
// Override home AFTER go_to so is_feed() still works.
add_filter(
'option_home',
function () {
- return 'http://example.com:8080/path?a=1&b=2';
+ return 'https://secure.example.com';
}
);
@@ -109,11 +142,69 @@ function () {
rsscloud_add_rss_cloud_element();
$output = ob_get_clean();
- // With proper escaping, '&' in attribute values should become '&'.
+ // The point of source:cloud: it carries the https endpoint that the
+ // port-80 / http-post cloud element above cannot express.
+ $this->assertStringContainsString(
+ '>https://secure.example.com/?rsscloud=notify',
+ $output
+ );
+
+ // The classic cloud element must stay on port 80 / http-post for
+ // older readers such as FeedLand.
+ $this->assertStringContainsString( "port='80'", $output );
+ $this->assertStringContainsString( "protocol='http-post'", $output );
+ }
+
+ public function test_source_cloud_url_is_escaped() {
+ $this->go_to( get_feed_link( 'rss2' ) );
+
+ add_filter(
+ 'option_home',
+ function () {
+ return 'https://example.com/path?a=1&b=2';
+ }
+ );
+
+ ob_start();
+ rsscloud_add_rss_cloud_element();
+ $output = ob_get_clean();
+
+ // Raw & is not well-formed XML in element content.
$this->assertStringNotContainsString( '&b=2', $output,
- 'Attribute values must be escaped; raw & should not appear' );
- $this->assertStringContainsString( '&', $output,
- 'Attribute values should use esc_attr() which converts & to &' );
+ 'source:cloud URL must be escaped; raw & should not appear' );
+ }
+
+ public function test_feed_with_source_cloud_is_well_formed_xml() {
+ $this->go_to( get_feed_link( 'rss2' ) );
+
+ ob_start();
+ rsscloud_add_rss_cloud_element();
+ $output = ob_get_clean();
+
+ // Wrap the fragment the way feed-rss2.php does, including another
+ // plugin binding the source prefix to a DIFFERENT uri on the rss tag,
+ // and confirm the result still parses and resolves correctly.
+ $xml = ''
+ . ''
+ . 't' . $output . '';
+
+ $prev = libxml_use_internal_errors( true );
+ libxml_clear_errors();
+ $doc = simplexml_load_string( $xml );
+ $errors = libxml_get_errors();
+ libxml_clear_errors();
+ libxml_use_internal_errors( $prev );
+
+ $this->assertNotFalse( $doc, 'Feed fragment must be well-formed XML' );
+ $this->assertEmpty( $errors, 'Feed fragment must parse without XML errors' );
+
+ // The inline declaration must win over the rss tag's binding.
+ $source = $doc->channel->children( 'https://source.scripting.com/' );
+ $this->assertNotEmpty(
+ (string) $source->cloud,
+ 'source:cloud must resolve to the scripting.com namespace, not the rss tag binding'
+ );
+ $this->assertStringContainsString( 'rsscloud=notify', (string) $source->cloud );
}
public function test_parse_request_does_nothing_without_rsscloud_var() {
diff --git a/tests/test-send-post-notifications.php b/tests/test-send-post-notifications.php
index c903628..62115b0 100644
--- a/tests/test-send-post-notifications.php
+++ b/tests/test-send-post-notifications.php
@@ -274,37 +274,4 @@ function () use ( &$fired ) {
$this->assertTrue( $fired );
}
-
- public function test_nonstandard_port_is_whitelisted_for_url_validation() {
- // No pre_http_request mock — we want wp_http_validate_url to run so
- // the plugin's http_allowed_safe_ports filter gets applied. Hook at
- // priority 999 to observe what the validator sees.
- $observed_ports = array();
- add_filter(
- 'http_allowed_safe_ports',
- function ( $ports ) use ( &$observed_ports ) {
- $observed_ports = $ports;
- return $ports;
- },
- 999
- );
-
- // TEST-NET-3 IP (RFC 5737) — wp_http_validate_url skips DNS for
- // literal IPs and reaches the port check where our filter fires.
- $this->set_notifications(
- $this->build_notifications(
- array(
- 'notify_url' => 'http://203.0.113.5:4000/feedupdated',
- )
- )
- );
-
- rsscloud_send_post_notifications( $this->feed_url );
-
- $this->assertContains(
- 4000,
- $observed_ports,
- 'Plugin should add the subscriber port to http_allowed_safe_ports before sending.'
- );
- }
}