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

# Copyright 2005-2006 Messiah College. All rights reserved.
# Jason Long <jlong@messiah.edu>

# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.

use strict;
use warnings;

package Mail::DKIM::Canonicalization::DkCommon;
use base 'Mail::DKIM::Canonicalization::Base';
use Carp;

sub init {
    my $self = shift;
    $self->SUPER::init;

    $self->{header_count} = 0;
}

# similar to code in DkimCommon.pm
sub add_header {

    #Note: canonicalization of headers is performed
    #in finish_header()

    my $self = shift;
    $self->{header_count}++;
}

sub finish_header {
    my $self = shift;
    my %args = @_;

    # RFC4870, 3.3:
    #   h = A colon-separated list of header field names that identify the
    #       headers presented to the signing algorithm. If present, the
    #       value MUST contain the complete list of headers in the order
    #       presented to the signing algorithm.
    #
    #       In the presence of duplicate headers, a signer may include
    #       duplicate entries in the list of headers in this tag.  If a
    #       header is included in this list, a verifier must include all
    #       occurrences of that header, subsequent to the "DomainKey-
    #       Signature:" header in the verification.
    #
    # RFC4870, 3.4.2.1:
    #   * Each line of the email is presented to the signing algorithm in
    #     the order it occurs in the complete email, from the first line of
    #     the headers to the last line of the body.
    #   * If the "h" tag is used, only those header lines (and their
    #     continuation lines if any) added to the "h" tag list are included.

    # only consider headers AFTER my signature
    my @sig_headers;
    {
        my $s0 = @{ $args{Headers} } - $self->{header_count};
        my $s1 = @{ $args{Headers} } - 1;
        @sig_headers = ( @{ $args{Headers} } )[ $s0 .. $s1 ];
    }

    # check if signature specifies a list of headers
    my @sig_header_names = $self->{Signature}->headerlist;
    if (@sig_header_names) {

        # - first, group all header fields with the same name together
        #   (using a hash of arrays)
        my %heads;
        foreach my $line (@sig_headers) {
            next unless $line =~ /^([^\s:]+)\s*:/;
            my $field_name = lc $1;

            $heads{$field_name} ||= [];
            push @{ $heads{$field_name} }, $line;
        }

        # - second, count how many times each header field name appears
        #   in the h= tag
        my %counts;
        foreach my $field_name (@sig_header_names) {
            $heads{ lc $field_name } ||= [];
            $counts{ lc $field_name }++;
        }

        # - finally, working backwards through the h= tag,
        #   collect the headers we will be signing (last to first).
        #   Normally, one occurrence of a name in the h= tag
        #   correlates to one occurrence of that header being presented
        #   to canonicalization, but if (working backwards) we are
        #   at the first occurrence of that name, and there are
        #   multiple headers of that name, then put them all in.
        #
        @sig_headers = ();
        while ( my $field_name = pop @sig_header_names ) {
            $counts{ lc $field_name }--;
            if ( $counts{ lc $field_name } > 0 ) {

                # this field is named more than once in the h= tag,
                # so only take the last occuring of that header
                my $line = pop @{ $heads{ lc $field_name } };
                unshift @sig_headers, $line if defined $line;
            }
            else {
                unshift @sig_headers, @{ $heads{ lc $field_name } };
                $heads{ lc $field_name } = [];
            }
        }
    }

    # iterate through each header, in the order determined above
    foreach my $line (@sig_headers) {
        if ( $line =~ /^(from|sender)\s*:(.*)$/i ) {
            my $field   = $1;
            my $content = $2;
            $self->{interesting_header}->{ lc $field } = $content;
        }
        $line =~ s/\015\012\z//s;
        $self->output( $self->canonicalize_header( $line . "\015\012" ) );
    }

    $self->output( $self->canonicalize_body("\015\012") );
}

sub add_body {
    my $self = shift;
    my ($multiline) = @_;

    $self->output( $self->canonicalize_body($multiline) );
}

sub finish_body {
}

sub finish_message {
}

1;