package LWP::Authen;
#use Data::Dumper;

use strict;
use vars qw(@EXPORT_OK @AUTH_PREF);

@AUTH_PREF=qw(digest basic);

require HTTP::Headers::Auth;


sub spool_handler
{
    my($ua, $req) = @_;
    my $realm = $ua->uri_attr_plain($req->url, "realm");
    return unless $realm;
    my $realms = $ua->uri_attr_plain($req->url, "realms");
    # should we ensure that this is a SERVER attribute?
    unless ($realms) {
	warn "No REALMS registered for this server";
	return;
    }
    if (my $auth = $realms->{$realm}) {
	$auth->set_authorization($req);
    } else {
	warn "Don't know about the '$realm' realm";
    }
    0;
}


sub response_handler
{
    my($req, $res) = @_;
    my $proxy;
    my $code = $res->code;
    my $header;
    if ($code == 401) {
	$header = "WWW-Authenticate";
    } elsif ($code == 407) {
	$header = "Proxy-Authenticate";
	$proxy++;
    } else {
	return;
    }

    my %auth = $res->_authenticate($header);
    unless (keys %auth) {
	$res->push_header("Client-Warning" => 
			  "Missing $header header in $code response");
	return;
    }

    # make an array with the authentication schemes in preferred order
    my @auth;
    for (@AUTH_PREF) {
	if (my $auth = delete $auth{lc $_}) {
	    push(@auth, [$_, $auth]);
	}
    }
    # try the rest too, in case we know how to handle it.
    # XXX should really keep the order specified by the server, so
    # filtering it through a hash is probably not such a good idea.
    for (keys %auth) {
	push(@auth, [$_, $auth{$_}]);
    }
    undef(%auth);

    for (@auth) {
	my($scheme, $param) = @$_;
	unless ($scheme =~ /^([a-z]+(?:-[a-z]+)*)$/) {
	    $res->push_header("Client-Warning" => 
			      "Bad authentication scheme name '\u$scheme'");
	    next;
	}

	$scheme = $1;  # untainted now too
	my $class = "LWP::Authen::$scheme";
	$class =~ s/-/_/g;
	
	no strict 'refs';
	unless (defined %{"$class\::"}) {
	    # try to load it
	    eval "require $class";
	    if ($@) {
		if ($@ =~ /^Can\'t locate/) {
		    $res->push_header("Client-Warning" =>
			   "Unsupported authentication scheme '\u$scheme'");
		} else {
		    chomp($@);
		    $res->push_header("Client-Warning" => $@);
		}
		next;
	    }
	}

	my $auth = $class->authenticate($req, $res, $proxy, $param);
	next unless $auth;
	return $auth unless ref($auth);

	# Try to make a new request which we add authorizaton to
	# using the returned auth-object.
	my $new = $req->clone;
	$new->{'previous'} = $res;
	$new->priority(10) if $new->priority > 10;

	if ($proxy) {
	    $auth->set_proxy_authorization($new);
	    # XXX: Check for repeated fail

	} else {
	    $auth->set_authorization($new);

	    # Check for repeated fail
	    my $digest1 = join("|",
			       $new->method,
			       $new->url,
			       $new->header("Authorization"));
	    my $count = 0;
	    for (my $r = $res; $r; $r = $r->previous) {
		my $req = $r->request;
		my $digest2 = join("|",
				   $req->method,
				   $req->url,
				   $req->header("Authorization"));
		if (++$count > 13) {
		    $res->push_header("Client-Warning" =>
				      "Probably redirect loop");
		    return "ABORT";
		    
		}
		if ($digest1 eq $digest2) {
		    $res->push_header("Client-Warning" =>
				      "Same credentials failed before");
		    return "ABORT";
		}
	    }

	    my $realm = $param->{"realm"} || "";
	    $req->{'mgr'}{'uattr'}->attr_update("DIR", $new->url)->{realm} = $realm;
	    $req->{'mgr'}{'uattr'}->attr_update("SERVER", $new->url)->{realms}{$realm} = $auth;
	}

	$req->{'mgr'}->spool($new);
	return "FOLLOWUP MADE";
    }
    return;  # not handled
}

1;
