Skip Menu |

Preferred bug tracker

Please visit the preferred bug tracker to report your issue.

This queue is for tickets about the WWW-Mechanize CPAN distribution.

Report information
The Basics
Id: 40780
Status: resolved
Priority: 0/
Queue: WWW-Mechanize

People
Owner: Nobody in particular
Requestors: corion [...] cpan.org
matt.lawrence [...] virgin.net
Cc:
AdminCc:

Bug Information
Severity: Critical
Broken in:
  • 1.20
  • 1.21_01
  • 1.21_02
  • 1.21_03
  • 1.21_04
  • 1.22
  • 1.24
  • 1.26
  • 1.29_01
  • 1.30
  • 1.31_01
  • 1.31_02
  • 1.32
  • 1.34
  • 1.49_01
  • 1.50
Fixed in: (no value)



Subject: API Conflict between LWP::UserAgent::credentials() and WWW::Mechanize::credentials
WWW::Mechanize 1.20+ implements a horrible monkeypatch globally overwriting LWP::UserAgent::credentials with a curried version that can only ever return one set of login/password. This breaks programs that use the LWP authentication mechanism. There is no way of retrieving the credentials once they have been set from WWW::Mechanize. Please don't step on other people's namespaces. The attached test file tests that the ->credentials() method is independent per WWW::Mechanize instance and doesn't affect LWP::UserAgent methods. -max
Subject: www-mechanize-monkeypatch-bug.t
#!/usr/bin/perl -w use strict; use Test::More tests => 4; use LWP::UserAgent; use WWW::Mechanize; =head1 NAME www-mechanize-monkeypatch-bug.t - Prevent ugly LWP::UserAgent monkeypatching =head1 REASON The monkeypatch introduced since at least WWW::Mechanize 1.34 only ever allows one instance of every LWP::UserAgent descendant to have credentials. This test checks that this buggy behaviour is gone. Also, the WWW::Mechanize API breaks LWP::UserAgent which always expects ->credentials($hostloc,$realm) to return a username and password. The solution would be to rename ->credentials() to something different that doesn't clash with LWP::UserAgent. =cut my $ua = LWP::UserAgent->new(); my $m_1 = WWW::Mechanize->new(); my $m_2 = WWW::Mechanize->new(); my $m_3 = WWW::Mechanize->new(); $m_1->credentials('m_1','m_1'); $m_2->credentials('m_2','m_2'); my @ua = $ua->credentials; isn't "@ua", "m_2 m_2", 'LWP::UserAgent instance retains its old credentials'; is_deeply [$m_1->get_basic_credentials], ['m_1','m_1'], 'First instance retains its credentials'; is_deeply [$m_2->get_basic_credentials], ['m_2','m_2'], 'Second instance retains its credentials'; is_deeply [$m_3->get_basic_credentials], [undef,undef], 'Untouched instance retains its credentials';
I spotted this recently too. Plus another icky problem where the latest LWP::UserAgent credentials method now also supports 2 arguments (a form which is also used internally by get_basic_credentials), so there's no longer an easy way to disambiguate the two interfaces. I have written a quick patch and a test for this.
#!perl -Tw use warnings; use strict; use WWW::Mechanize; use Test::More tests => 12; my $mech = new WWW::Mechanize; my ($user, $pass); my $uri = new URI 'http://localhost'; ($user, $pass) = $mech->get_basic_credentials('myrealm', $uri, 0); is $user, undef, 'default username is undefined at first'; is $pass, undef, 'default password is undefined at first'; $mech->credentials("username", "password"); ($user, $pass) = $mech->get_basic_credentials('myrealm', $uri, 0); is $user, 'username', 'calling credentials sets username for get_basic_credentials'; is $pass, 'password', 'calling credentials sets password for get_basic_credentials'; my $mech2 = $mech->clone; ($user, $pass) = $mech2->get_basic_credentials('myrealm', $uri, 0); is $user, 'username', 'cloned object has username for get_basic_credentials'; is $pass, 'password', 'cloned object has password for get_basic_credentials'; my $mech3 = new WWW::Mechanize; ($user, $pass) = $mech3->get_basic_credentials('myrealm', $uri, 0); is $user, undef, 'new object has no username for get_basic_credentials'; is $pass, undef, 'new object has no password for get_basic_credentials'; $mech->clear_credentials; ($user, $pass) = $mech->get_basic_credentials('myrealm', $uri, 0); is $user, undef, 'username is undefined after clear_credentials'; is $pass, undef, 'password is undefined after clear_credentials'; ($user, $pass) = $mech2->get_basic_credentials('myrealm', $uri, 0); is $user, 'username', 'cloned object still has username for get_basic_credentials'; is $pass, 'password', 'cloned object still has password for get_basic_credentials';
--- lib/WWW/Mechanize.pm 2008-10-27 04:06:15.000000000 +0000 +++ lib/WWW/Mechanize.pm 2008-11-14 12:45:36.000000000 +0000 @@ -2129,29 +2129,39 @@ =cut -{ - my $saved_method; +sub credentials { + my $self = shift; - sub credentials { - my $self = shift; - no warnings 'redefine'; ## no critic - - if (@_ == 4) { - $saved_method - and *LWP::UserAgent::get_basic_credentials = $saved_method; - return $self->SUPER::credentials(@_); - } + # The lastest LWP::UserAgent also supports 2 arguments, + # in which case the first is host:port + if (@_ == 4 || (@_ == 2 && $_[0] =~ /:\d+$/)) { + return $self->SUPER::credentials(@_); + } - @_ == 2 - or $self->die( 'Invalid # of args for overridden credentials()' ); + @_ == 2 + or $self->die( 'Invalid # of args for overridden credentials()' ); - my ($username, $password) = @_; - $saved_method ||= \&LWP::UserAgent::get_basic_credentials; - *LWP::UserAgent::get_basic_credentials - = sub { return $username, $password }; - } + return @$self{qw( __username __password )} = @_ +} + +# Overridden from LWP::UserAgent to implement global username and password +sub get_basic_credentials { + my $self = shift; + my @cred = grep { defined } @$self{qw( __username __password )}; + return @cred if @cred == 2; + return $self->SUPER::get_basic_credentials(@_); } +=head2 $mech->clear_credentials() + +Remove any credentials set up with credentials(). + +=cut + +sub clear_credentials { + my $self = shift; + delete @$self{qw( __username __password )}; +} =head1 INTERNAL-ONLY METHODS
Fixed in 1.51_02.