File Coverage

blib/lib/ThreatDetector/Parser.pm
Criterion Covered Total %
statement 18 18 100.0
branch 3 4 75.0
condition n/a
subroutine 4 4 100.0
pod 0 1 0.0
total 25 27 92.5


line stmt bran cond sub pod time code
1             package ThreatDetector::Parser;
2            
3 2     2   408478 use strict;
  2         4  
  2         92  
4 2     2   9 use warnings;
  2         5  
  2         150  
5 2     2   1148 use URI::Escape;
  2         3993  
  2         1270  
6            
7             our $VERBOSE = 0;
8             our $VERSION = '0.04';
9            
10             our %STATS = (
11             parsed => 0,
12             skipped => 0,
13             );
14            
15             sub parse_log_line {
16 2     2 0 230944 my ($line) = @_;
17 2         7 $line =~ s/\r//g;
18            
19 2 50       9 warn ">> parse_log_line() called with: [$line]\n" if $VERBOSE;
20            
21             # Common Log Format (with or without referer + user-agent)
22             # Example:
23             # 192.168.0.1 - - [20/Jun/2025:13:55:36 -0700] "GET /index.php?id=1 HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
24 2 100       26 if (
25             $line =~ m/^
26             (\d{1,3}(?:\.\d{1,3}){3}) # IP
27             \s+ \S+ \s+ \S+ # - - (unused)
28             \s+ \[([^\]]+)\] # [timestamp]
29             \s+ "(GET|POST|PUT|DELETE|HEAD|OPTIONS|PATCH)
30             \s+ ([^"]+?) # URI
31             \s+ HTTP\/[0-9.]+" # Protocol
32             \s+ (\d{3}) # Status
33             \s+ (\d+|-) # Size
34             \s+ "([^"]*)" # Referer
35             \s+ "([^"]*)" # User-Agent
36             /x
37             )
38             {
39 1         7 my ( $ip, $time, $method, $uri, $status, $size, $referer, $agent ) =
40             ( $1, $2, $3, uri_unescape($4), $5, $6, $7, $8 );
41            
42 1         39 $STATS{parsed}++;
43             return {
44 1         16 ip => $ip,
45             time => $time,
46             method => $method,
47             uri => $uri,
48             status => $status,
49             size => $size,
50             referer => $referer,
51             user_agent => $agent,
52             raw => $line,
53             };
54             }
55 1         4 $STATS{skipped}++;
56 1         6 return undef;
57             }
58            
59             1;
60            
61             =head1 NAME
62            
63             ThreatDetector::Parser - Apache log parser for threat detection
64            
65             =head1 SYNOPSIS
66            
67             use ThreatDetector::Parser;
68             my $entry = ThreatDetector::Parser::parse_log_line($line);
69            
70             =head1 DESCRIPTION
71            
72             Parses lines from an Apache access log and extracts structured request info.
73            
74             =head1 AUTHOR
75            
76             Jason Hall
77            
78             =cut