1

I am using the below code to show external rss feed in my wordpress blog page template:

<?php 
  include_once(ABSPATH . WPINC . '/rss.php');
  $rss = fetch_rss('http://feeds.feedburner.com/site-feed');
  $maxitems = 10;
  $items = array_slice($rss->items, 0, $maxitems);
?>
<ul class="arrow">
  <?php if (empty($items)): ?>
    <li>No items</li>
  <?php else:
      foreach ( $items as $item ):
        ?>
        <li>
          <a href='<?php echo $item['link']; ?>' title='<?php echo $item['title']; ?>'>
            <?php echo $item['title']; ?>
          </a>
        </li>
        <?php
      endforeach;
    endif;
  ?>

The above feed was earlier open but now is password protected. I already know the user name and password. How to tweak the above code to use user/name password?

  • What kind of password protection? Like http authentication? – birgire Aug 19 '15 at 15:04
2

I assume you mean fetching an external feed with an HTTP authentication.

Fetching feeds with HTTP Authentication

Note that fetch_rss() uses the MagPie library, that's deprecated in WordPress.

Use instead fetch_feed() that uses the SimplePie library.

It looks though it doesn't support this kind of url:

http://user:pass@example.tld/feed/

like we can use with curl in command-line, e.g.:

# curl -i http://user:pass@example.tld/feed/

The fetch_feed() function calls the wp_safe_remote_request() function, to fetch the feed. This is happens in the WP_SimplePie_File class, that's an extension of the SimplePie_File class.

That's good news, because it means we can use the http_request_args filter, to modify the request headers. If we check out the Codex page on the HTTP API, we see that in the external references, that there's a link to John Blackbourn's short but informative post about Basic Authentication with the WordPress HTTP API. There he gives the following example:

$args = array(
    'headers' => array(
        'Authorization' => 'Basic ' . base64_encode( YOUR_USERNAME . ':' . YOUR_PASSWORD )
    )
);
wp_remote_request( $url, $args );

to send the correct authentication header with the request.

Demo plugin

We can now add a support for the following url type:

$rss = fetch_feed( 'http://user:pass@example.tld/feed/' );

with the this demo plugin:

<?php
/**
 * Plugin Name: Fetch Feeds with http authentication
 * Description: Allows feeds urls like http://user:pass@example.tld/feed/
 * Plugin URI:  http://wordpress.stackexchange.com/a/199101/26350
 */    
add_action( 'wp_feed_options', function( $feed, $url )
{
    $user = parse_url( $feed->feed_url, PHP_URL_USER );
    $pass = parse_url( $feed->feed_url, PHP_URL_PASS );

    // Nothing to do
    if( ! $user || ! $pass )
        return;

    // Remove the user:pass@ part from the feed url
    $feed->feed_url = str_replace( 
        sprintf( '%s:%s@', $user, $pass ), 
        '', 
        $feed->feed_url
    );

    add_filter( 'http_request_args', function( $r, $url ) use ( $user, $pass )
    {
        // Add the user & pass to the request's header
        if( $user && $pass )
            $r['headers']['Authorization'] = 'Basic ' . base64_encode(  $user . ':' . $pass );
        return $r;
    }, 10, 2 );     

}, 10, 2 );

Update:

This is how the error looks like:

WP HTTP Error: A valid URL was not provided.

when we run:

$rss = fetch_feed( 'http://user:pass@example.tld/feed/' );

It comes from the WP_Http::request() method:

if ( empty( $url ) || empty( $arrURL['scheme'] ) )
    return new WP_Error('http_request_failed', __('A valid URL was not provided.'));

Here's how the wp_safe_remote_request() function is defined:

function wp_safe_remote_request( $url, $args = array() ) {
        $args['reject_unsafe_urls'] = true;
        $http = _wp_http_get_object();
        return $http->request( $url, $args );
}

I was just playing with it and tested:

$args['reject_unsafe_urls'] = false;

through:

add_action( 'wp_feed_options', function( $feed )
{
    $user = parse_url( $feed->feed_url, PHP_URL_USER );
    $pass = parse_url( $feed->feed_url, PHP_URL_PASS );

    if( ! $user || ! $pass )
        return;

    add_filter( 'http_request_args', function( $r, $url ) use ( $user, $pass )
    {
        if( $user && $pass )
            $r['reject_unsafe_urls'] = false;
        return $r;
    }, 10, 2 );     
} );

and then this worked:

$rss = fetch_feed( 'http://user:pass@example.tld/feed/' );

Inside the WP_Http::request() method we got this condition:

if ( $r['reject_unsafe_urls'] )
    $url = wp_http_validate_url( $url );

so it looks like wp_http_validate_url() is not validating this url:

http://user:pass@example.tld/feed/

By setting reject_unsafe_urls to false, we skip that check.

There's actually a filter available for this: http_request_reject_unsafe_urls, but I wouldn't recommend using:

add_filter( `http_request_reject_unsafe_urls`, '__return_false' );

I think the demo plugin above, would in general be a better choice, because it still uses the wp_http_validate_url() on the auth stripped url.

Note:

This part:

if ( isset( $parsed_url['user'] ) || isset( $parsed_url['pass'] ) )
    return false;

within the wp_http_validate_url() function , seems to be the show-stopper for fetch_feed() for urls with user:pass.

  • Thank you @kaiser. The wp_http_validate_url() seems to be the show-stopper for urls like http://user:pass@example.tld/feed/. I don't know if that's unintentional or by design? – birgire Aug 20 '15 at 9:15
  • 1
    Well, I would assume that wp_kses_bad_protocol() is what makes this fail. Aside from that, I personally use the WP HTTP API, then load everything in \DOMDocument as 1.0 and UTF-8 and load it with loadXML. From there on, it's easy to read the feed using \DOMXPath::query() or just getElementsByTagName() or the namespaced variants. On top, it allows to use XSD documents to validate the feed and prevent tags that aren't supposed to be in there. That means that it's hard to break on the remote side without knowing it on our side. – kaiser Aug 20 '15 at 10:29
  • I dug some further and found that it passes wp_kses_bad_protocol() but stops within wp_http_validate_url(), where there's an explicit check for user+pass. That's an interesting approach for dealing with the feeds, you describe. Sometimes when I have to deal with lots of feeds, I've just used curl in cron + simplexml_load_file() ;-) @kaiser – birgire Aug 20 '15 at 13:18
  • 1
    The problem with plain curl is, that it misses the Certs shipped with WP core. Therefore it might use older Certs (from the OS install on the server) than the rest of the App. This can lead to "funny" results. – kaiser Aug 20 '15 at 13:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.