Email-Abuse-Investigator

 view release on metacpan or  search on metacpan

lib/Email/Abuse/Investigator.pm  view on Meta::CPAN

#   header name.
=head2 header_value

Returns the value of the first occurrence of a named header field, or
C<undef> if the header is absent.  The name comparison is case-insensitive.

=head3 API SPECIFICATION

  Input:  name => Str  (required) - header field name, e.g. 'Subject'
  Output: Str | undef

=head3 MESSAGES

  (none - returns undef on missing header, never throws)

=cut

sub header_value {
	my ($self, $name) = @_;
	return $self->_header_value($name);
}

#
# _header_value( $name ) -> string | undef
#
# Purpose:
#   Internal implementation for header_value().  Walks _headers list and
#   returns the value of the first matching header.
#
# Entry criteria:
#   $name -- a lower-cased header name string.
#   $self->{_headers} populated by _split_message().
#
# Exit status:
#   Returns the value string, or undef if the header is not present.

sub _header_value :Private {
	my ($self, $name) = @_;
	for my $h (@{ $self->{_headers} }) {
		return $h->{value} if $h->{name} eq lc($name);
	}
	return;
}

# _ip_in_cidr( $ip, $cidr ) -> bool
#
# Purpose:
#   Test whether an IPv4 address falls within a CIDR block or is an exact
#   match (when $cidr contains no '/' separator).
#
# Entry criteria:
#   $ip   -- a defined dotted-quad IPv4 address string.
#   $cidr -- a CIDR string like '10.0.0.0/8' or an exact IP.
#
# Exit status:
#   Returns 1 (true) if the IP is within the CIDR block, 0 otherwise.

sub _ip_in_cidr :Private {
	my ($self, $ip, $cidr) = @_;
	return $ip eq $cidr unless $cidr =~ m{/};
	my ($net_addr, $prefix) = split m{/}, $cidr;
	return 0 if !defined($prefix) || $prefix !~ /^\d+$/ || $prefix > 32;

	# Compute the network mask and compare masked network addresses
	my $mask  = ~0 << (32 - $prefix);
	my $net_n = unpack 'N', (inet_aton($net_addr) // return 0);
	my $ip_n  = unpack 'N', (inet_aton($ip)       // return 0);
	return ($ip_n & $mask) == ($net_n & $mask);
}

# _decode_mime_words( $str ) -> decoded_string
#
# Purpose:
#   Decode MIME encoded-words (=?charset?B/Q?...?=) in a header value
#   string for human-readable display in reports.
#
# Entry criteria:
#   $str -- a defined header value string; may be undef.
#
# Exit status:
#   Returns the decoded string, or '' if $str is undef.

sub _decode_mime_words :Private {
	my ($self, $str) = @_;
	return '' unless defined $str;
	# Replace each encoded-word with its decoded equivalent
	$str =~ s/=\?([^?]+)\?([BbQq])\?([^?]*)\?=/_decode_ew($1,$2,$3)/ge;
	return $str;
}

# _decode_ew( $charset, $enc, $text ) -> decoded_bytes
#
# Purpose:
#   Decode a single MIME encoded-word component (base64 or quoted-printable).
#
# Notes:
#   Non-UTF-8 charsets return raw bytes; good enough for display-name spoof
#   detection which only needs ASCII matching.

sub _decode_ew :Private {
	my ($charset, $enc, $text) = @_;
	my $raw;
	if (uc($enc) eq 'B') {
		$raw = decode_base64($text);
	} else {
		# Quoted-printable encoded-word uses underscore for space
		$text =~ s/_/ /g;
		$raw  = decode_qp($text);
	}
	return $raw;
}

# _parse_date_to_epoch( $str ) -> epoch_int | undef
#
# Purpose:
#   Parse common WHOIS date strings to a Unix epoch integer.
#   Handles YYYY-MM-DD, YYYY-MM-DDThh:mm:ssZ, and DD-Mon-YYYY formats.
#
# Entry criteria:
#   $str -- a defined date string; may be undef.
#



( run in 1.174 second using v1.01-cache-2.11-cpan-71847e10f99 )