Skip to content
Snippets Groups Projects
Commit 70f3c976 authored by Dave Reid's avatar Dave Reid
Browse files

Issue #723548: Added support for feed URLs with feed:// and webcal://.

parent b365f35f
No related branches found
No related tags found
No related merge requests found
...@@ -928,3 +928,34 @@ function feeds_alter($type, &$data) { ...@@ -928,3 +928,34 @@ function feeds_alter($type, &$data) {
/** /**
* @} * @}
*/ */
/**
* Copy of valid_url() that supports the webcal scheme.
*
* @see valid_url().
*
* @todo Replace with valid_url() when http://drupal.org/node/1191252 is fixed.
*/
function feeds_valid_url($url, $absolute = FALSE) {
if ($absolute) {
return (bool) preg_match("
/^ # Start at the beginning of the text
(?:ftp|https?|feed|webcal):\/\/ # Look for ftp, http, https, feed or webcal schemes
(?: # Userinfo (optional) which is typically
(?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password
(?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination
)?
(?:
(?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address
|(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address
)
(?::[0-9]+)? # Server port number (optional)
(?:[\/|\?]
(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional)
*)?
$/xi", $url);
}
else {
return (bool) preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
}
}
...@@ -121,20 +121,36 @@ function http_request_get($url, $username = NULL, $password = NULL, $accept_inva ...@@ -121,20 +121,36 @@ function http_request_get($url, $username = NULL, $password = NULL, $accept_inva
} }
} }
// Support the 'feed' and 'webcal' schemes by converting them into 'http'.
$url = strtr($url, array('feed://' => 'http://', 'webcal://' => 'http://'));
if ($curl) { if ($curl) {
$headers[] = 'User-Agent: Drupal (+http://drupal.org/)'; $headers[] = 'User-Agent: Drupal (+http://drupal.org/)';
$result = new stdClass(); $result = new stdClass();
// Only download via cURL if we can validate the scheme to be either http or // Parse the URL and make sure we can handle the schema.
// https. // cURL can only support either http:// or https://.
// Validate in PHP, CURLOPT_PROTOCOLS is only supported with cURL 7.19.4 // CURLOPT_PROTOCOLS is only supported with cURL 7.19.4
$uri = parse_url($url); $uri = parse_url($url);
if (isset($uri['scheme']) && $uri['scheme'] != 'http' && $uri['scheme'] != 'https') { if (!isset($uri['scheme'])) {
$result->error = 'invalid schema '. $uri['scheme']; $result->error = 'missing schema';
$result->code = -1003; // This corresponds to drupal_http_request() $result->code = -1002;
} }
else { else {
switch ($uri['scheme']) {
case 'http':
case 'https':
// Valid scheme.
break;
default:
$result->error = 'invalid schema ' . $uri['scheme'];
$result->code = -1003;
break;
}
}
// If the scheme was valid, continue to request the feed using cURL.
if (empty($result->error)) {
$download = curl_init($url); $download = curl_init($url);
curl_setopt($download, CURLOPT_FOLLOWLOCATION, TRUE); curl_setopt($download, CURLOPT_FOLLOWLOCATION, TRUE);
if (!empty($username)) { if (!empty($username)) {
......
...@@ -148,7 +148,11 @@ class FeedsHTTPFetcher extends FeedsFetcher { ...@@ -148,7 +148,11 @@ class FeedsHTTPFetcher extends FeedsFetcher {
* Override parent::sourceFormValidate(). * Override parent::sourceFormValidate().
*/ */
public function sourceFormValidate(&$values) { public function sourceFormValidate(&$values) {
if ($this->config['auto_detect_feeds']) { if (!feeds_valid_url($values['source'], TRUE)) {
$form_key = 'feeds][' . get_class($this) . '][source';
form_set_error($form_key, t('The URL %source is invalid.', array('%source' => $values['source'])));
}
elseif ($this->config['auto_detect_feeds']) {
feeds_include_library('http_request.inc', 'http_request'); feeds_include_library('http_request.inc', 'http_request');
if ($url = http_request_get_common_syndication($values['source'])) { if ($url = http_request_get_common_syndication($values['source'])) {
$values['source'] = $url; $values['source'] = $url;
......
...@@ -163,8 +163,8 @@ abstract class FeedsProcessor extends FeedsPlugin { ...@@ -163,8 +163,8 @@ abstract class FeedsProcessor extends FeedsPlugin {
$messages[] = array( $messages[] = array(
'message' => format_plural( 'message' => format_plural(
$state->created, $state->created,
'Created @number @entity', 'Created @number @entity.',
'Created @number @entities', 'Created @number @entities.',
array('@number' => $state->created) + $tokens array('@number' => $state->created) + $tokens
), ),
); );
...@@ -173,8 +173,8 @@ abstract class FeedsProcessor extends FeedsPlugin { ...@@ -173,8 +173,8 @@ abstract class FeedsProcessor extends FeedsPlugin {
$messages[] = array( $messages[] = array(
'message' => format_plural( 'message' => format_plural(
$state->updated, $state->updated,
'Updated @number @entity', 'Updated @number @entity.',
'Updated @number @entities', 'Updated @number @entities.',
array('@number' => $state->updated) + $tokens array('@number' => $state->updated) + $tokens
), ),
); );
...@@ -183,8 +183,8 @@ abstract class FeedsProcessor extends FeedsPlugin { ...@@ -183,8 +183,8 @@ abstract class FeedsProcessor extends FeedsPlugin {
$messages[] = array( $messages[] = array(
'message' => format_plural( 'message' => format_plural(
$state->failed, $state->failed,
'Failed importing @number @entity', 'Failed importing @number @entity.',
'Failed importing @number @entities', 'Failed importing @number @entities.',
array('@number' => $state->failed) + $tokens array('@number' => $state->failed) + $tokens
), ),
'level' => WATCHDOG_ERROR, 'level' => WATCHDOG_ERROR,
...@@ -443,9 +443,9 @@ abstract class FeedsProcessor extends FeedsPlugin { ...@@ -443,9 +443,9 @@ abstract class FeedsProcessor extends FeedsPlugin {
); );
global $user; global $user;
$formats = filter_formats($user); $formats = filter_formats($user);
foreach ($formats as $format) { foreach ($formats as $format) {
$format_options[$format->format] = check_plain($format->name); $format_options[$format->format] = check_plain($format->name);
} }
$form['input_format'] = array( $form['input_format'] = array(
'#type' => 'select', '#type' => 'select',
'#title' => t('Text format'), '#title' => t('Text format'),
......
...@@ -31,12 +31,7 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase { ...@@ -31,12 +31,7 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase {
// text on nodes will fail. // text on nodes will fail.
$edit = array('fields[body][type]' => 'text_default'); $edit = array('fields[body][type]' => 'text_default');
$this->drupalPost('admin/structure/types/manage/article/display/teaser', $edit, 'Save'); $this->drupalPost('admin/structure/types/manage/article/display/teaser', $edit, 'Save');
}
/**
* Test node creation, refreshing/deleting feeds and feed items.
*/
public function test() {
// Create an importer configuration. // Create an importer configuration.
$this->createImporterConfiguration('Syndication', 'syndication'); $this->createImporterConfiguration('Syndication', 'syndication');
$this->addMappings('syndication', $this->addMappings('syndication',
...@@ -68,7 +63,12 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase { ...@@ -68,7 +63,12 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase {
), ),
) )
); );
}
/**
* Test node creation, refreshing/deleting feeds and feed items.
*/
public function test() {
$nid = $this->createFeedNode(); $nid = $this->createFeedNode();
// Assert 10 items aggregated after creation of the node. // Assert 10 items aggregated after creation of the node.
...@@ -81,9 +81,6 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase { ...@@ -81,9 +81,6 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase {
// Navigate to a non-feed node, there should be no Feeds tabs visible. // Navigate to a non-feed node, there should be no Feeds tabs visible.
$article_nid = db_query_range("SELECT nid FROM {node} WHERE type = 'article'", 0, 1)->fetchField(); $article_nid = db_query_range("SELECT nid FROM {node} WHERE type = 'article'", 0, 1)->fetchField();
$this->drupalGet('node/'. $article_nid);
$this->assertNoRaw('node/'. $article_nid .'/import');
$this->assertNoRaw('node/'. $article_nid .'/delete-items');
$this->assertEqual("Created by FeedsNodeProcessor", db_query("SELECT nr.log FROM {node} n JOIN {node_revision} nr ON n.vid = nr.vid WHERE n.nid = :nid", array(':nid' => $article_nid))->fetchField()); $this->assertEqual("Created by FeedsNodeProcessor", db_query("SELECT nr.log FROM {node} n JOIN {node_revision} nr ON n.vid = nr.vid WHERE n.nid = :nid", array(':nid' => $article_nid))->fetchField());
// Assert accuracy of aggregated information. // Assert accuracy of aggregated information.
...@@ -365,4 +362,48 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase { ...@@ -365,4 +362,48 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase {
$this->assertText('Fri, 09/18/2009'); $this->assertText('Fri, 09/18/2009');
$this->assertText('The first major change is switching'); $this->assertText('The first major change is switching');
} }
/**
* Test validation of feed URLs.
*/
function testFeedURLValidation() {
$edit['feeds[FeedsHTTPFetcher][source]'] = 'invalid://url';
$this->drupalPost('node/add/page', $edit, 'Save');
$this->assertText('The URL invalid://url is invalid.');
}
/**
* Test using non-normal URLs like feed:// and webcal://.
*/
function testOddFeedSchemes() {
$url = $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') .'/tests/feeds/developmentseed.rss2';
$schemes = array('feed', 'webcal');
$item_count = 0;
foreach ($schemes as $scheme) {
$feed_url = strtr($url, array('http://' => $scheme . '://', 'https://' => $scheme . '://'));
$edit['feeds[FeedsHTTPFetcher][source]'] = $feed_url;
$this->drupalPost('node/add/page', $edit, 'Save');
$this->assertText('Basic page Development Seed - Technological Solutions for Progressive Organizations has been created.');
$this->assertText('Created 10 nodes.');
$this->assertFeedItemCount($item_count + 10);
$item_count += 10;
}
}
/**
* Test that feed elements and links are not found on non-feed nodes.
*/
function testNonFeedNodeUI() {
// There should not be feed links on an article node.
$non_feed_node = $this->drupalCreateNode(array('type' => 'article'));
$this->drupalGet('node/'. $non_feed_node->nid);
$this->assertNoLink('Import');
$this->assertNoLink('Delete items');
// Navigat to a non-feed node form, there should be no Feed field visible.
$this->drupalGet('node/add/article');
$this->assertNoFieldByName('feeds[FeedsHTTPFetcher][source]');
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment