The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl

use strict;
use warnings;

use Test::More;

use HTTP::Cookies ();
use HTTP::Request ();
use HTTP::Response ();
use URI ();

my $jar = HTTP::Cookies->new();

plan tests => 13;

# https://www.rfc-editor.org/rfc/rfc2965.txt
# Section 4

# Example 1
{
    # 1-2
    # Most detail of request and response headers has been omitted.  Assume
    # the user agent has no stored cookies.
    # 1. User Agent -> Server
    #   POST /acme/login HTTP/1.1
    #   [form data]
    #   User identifies self via a form.
    # 2. Server -> User Agent
    #   HTTP/1.1 200 OK
    #   Set-Cookie2: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"
    #   Cookie reflects user's identity.
    my $res = HTTP::Response->new( 200, 'OK' );
    $res->header('Set-Cookie2' => q{Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"});
    my $req = request_for('www.acme.com/acme/login', 'POST');
    # we can skip the form data as it's not necessary here
    $res->request($req);
    $jar->extract_cookies($res);
    is(count_cookies_for('www.acme.com'), 1, '1.1-2: res: found the cookie');

    # 3-4
    # 3. User Agent -> Server
    #  POST /acme/pickitem HTTP/1.1
    #  Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"
    #  [form data]
    #  User selects an item for "shopping basket".
    # 4. Server -> User Agent
    #  HTTP/1.1 200 OK
    #  Set-Cookie2: Part_Number="Rocket_Launcher_0001"; Version="1"; Path="/acme"
    #  Shopping basket contains an item.
    $req = request_for('www.acme.com/acme/pickitem', 'POST');
    $jar->add_cookie_header($req);
    my $h = $req->header("Cookie"); # checking header contents is easier
    like($h, qr/Customer="?WILE_E_COYOTE"?/, '1.3-4: req: contains header');
    $res->header('Set-Cookie2' => q{Part_Number="Rocket_Launcher_0001"; Version="1"; Path="/acme"});
    $res->request($req);
    $jar->extract_cookies($res);
    is(count_cookies_for('www.acme.com'), 2, '1.3-4: res: found the cookies');

    # 5-6
    # 5. User Agent -> Server
    #  POST /acme/shipping HTTP/1.1
    #  Cookie: $Version="1";
    #    Customer="WILE_E_COYOTE"; $Path="/acme";
    #    Part_Number="Rocket_Launcher_0001"; $Path="/acme"
    #  [form data]
    #  User selects shipping method from form.
    # 6. Server -> User Agent
    #  HTTP/1.1 200 OK
    #  Set-Cookie2: Shipping="FedEx"; Version="1"; Path="/acme"
    #  New cookie reflects shipping method.
    $req = request_for('www.acme.com/acme/shipping', 'POST');
    $jar->add_cookie_header($req);
    $h = $req->header("Cookie"); # checking header contents is easier
    like($h, qr/Customer="?WILE_E_COYOTE"?/, '1.5-6: req: contains cust');
    like($h, qr/Part_Number="?Rocket_Launcher_0001"?/, '1.5-6: req: contains part');
    $res->header('Set-Cookie2' => q{Shipping="FedEx"; Version="1"; Path="/acme"});
    $res->request($req);
    $jar->extract_cookies($res);
    is(count_cookies_for('www.acme.com'), 3, '1.5-6: res: found the cookies');

    # 7-8
    # 7. User Agent -> Server
    #  POST /acme/process HTTP/1.1
    #  Cookie: $Version="1";
    #    Customer="WILE_E_COYOTE"; $Path="/acme";
    #    Part_Number="Rocket_Launcher_0001"; $Path="/acme";
    #    Shipping="FedEx"; $Path="/acme"
    #  [form data]
    #  User chooses to process order.
    # 8. Server -> User Agent
    #  HTTP/1.1 200 OK
    #  Transaction is complete.
    $req = request_for('www.acme.com/acme/process', 'POST');
    $jar->add_cookie_header($req);
    $h = $req->header("Cookie"); # checking header contents is easier
    like($h, qr/Customer="?WILE_E_COYOTE"?/, '1.7-8: req: contains cust');
    like($h, qr/Part_Number="?Rocket_Launcher_0001"?/, '1.7-8: req: contains part');
    like($h, qr/Shipping="?FedEx"?/, '1.7-8: req: contains shipping');
    $res = HTTP::Response->new( 200, 'OK' );
    $res->request($req);
    $jar->extract_cookies($res);
    is(count_cookies_for('www.acme.com'), 3, '1.7-8: res: found the cookies');
}

# Example 2
#This example illustrates the effect of the Path attribute.  All
#   detail of request and response headers has been omitted.  Assume the
#   user agent has no stored cookies.
{
    $jar->clear();
    my $res = HTTP::Response->new( 200, 'OK' );
    # requests to /acme get rocket launcher
    $res->push_header('Set-Cookie2' => q{Part_Number="Rocket_Launcher_0001"; Version="1"; Path="/acme"});
    # requests to /acme/ammo will get rocket launcher and rocket
    $res->push_header('Set-Cookie2' => q{Part_Number="Riding_Rocket_0023"; Version="1"; Path="/acme/ammo"});
    # requests to /acme/anything_else will just get rocket launcher

    my $req = request_for('www.acme.com/acme/', 'POST');
    $res->request($req);
    $jar->extract_cookies($res);
    is(count_cookies_for('www.acme.com'), 1, '2: acme: res: one cookie');

    $jar->clear();
    $req = request_for('www.acme.com/acme/ammo', 'POST');
    $res->request($req);
    $jar->extract_cookies($res);
    is(count_cookies_for('www.acme.com'), 2, '2: acme/ammo: res: two cookies');

    $jar->clear();
    $req = request_for('www.acme.com/acme/parts', 'POST');
    $res->request($req);
    $jar->extract_cookies($res);
    is(count_cookies_for('www.acme.com'), 1, '2: acme/parts: res: one cookie');
}

sub count_cookies_for {
    my $host = shift;
    my $count = 0;
    $jar->scan(sub { $_[4] eq $host && $count++ });
    return $count;
}

sub request_for {
    my $uri = URI->new('http://'.shift);
    my $method = shift || 'GET';
    my $req = HTTP::Request->new($method => $uri);
    $req->header(Host => $uri->host_port);
    return $req;
}