#!/usr/bin/env perl $g_version = "1.0 20221022"; $g_auther_name = "Aikinsetsu Yuaiho"; $g_auther_url = "https://yuaiho.com/"; ##### ======================================== ### Load Modules ##### ---------------------------------------- # Below modules may be installed. ## Module of getting current Directory use Cwd; # Bellow modules may be not installed as default. use Time::Local 'timelocal'; use TimeDate; ##### ======================================== ##### ======================================== ### Configurations and preference ##### ---------------------------------------- #### Preferences ### ** is Program Dfault. ### Timezone offset [sec] between UTC/GMT and final destination server location. ### This program does not support DST/daylight saving time processing. ## ** $g_pref_tz_sec = 9*3600*(1); ## Note: Default parameter means shipment default. Dose not mean recommended. ## Setting examples #$g_pref_tz_sec = 9*3600*(1); #UTC+9/JST(Japan) #$g_pref_tz_sec = 8*3600*(-1); #UTC-9/PST(USA) #$g_pref_tz_sec = 6.5*3600*(1); #UTC+6:30/MMT(Myanmar) $g_pref_tz_sec = 9*3600*(1); #UTC+9/JST(Japan) ### Match header ## 0: Use Received: header only. Cannot use for "Sent" / "Draft" / "Sent mesages". ## 1: Use Date: header only. ## ** 2: Use both. Use Received: header if it's found, however use Data: header. $g_pref_mode_header = 2; ### Parse header section only. ## This parameter available only $g_pref_mode_header is set 0 or 2. ## 0: Find time stamp across mail file. ## ** 1: Find only mailheader. (Parse stops when blank line(^$) was found.) $g_pref_mode_parse_headeronly = 1; #### Shell program paths # Path of touch command. # ** "/usr/bin/touch -d" # Note: Default value is FreeBSD default. -d option is required. $g_path_prog_touch = "/usr/bin/touch -d"; # Path of echo command # ** "/bin/echo" # Note: Default value is FreeBSD default. $g_path_prog_echo = "/bin/echo"; ##### ======================================== ##### ======================================== ### how to use ##### ---------------------------------------- # This program expect that user inputs mail file list. # Notice: Add # # Run sample # [user@host ~/]$ cat | # [user@host mailboxdir]$ find ./cur -type f | ##### ======================================== ##### ======================================== ### Arguments routines ##### ======================================== @args = @ARGV; for ($i=0; $i<=$#ARGV; $i++){ if ($args[$i] eq "--debug"){ $g_mode_debug = 1; }elsif($args[$i] eq "--commit"){ $g_mode_commit = 1; }elsif($args[$i] eq "--noinfo"){ $g_mode_noinfo = 1; }elsif($args[$i] eq "--absolute"){ $g_mode_absolute = 1; }elsif( ($args[$i] eq "--help") || ($args[$i] eq "--usage") || ($args[$i] eq "-h") ){ &show_usage; exit; }else{ print STDERR "ERROR: Argument \"$args[$i]\" is not implemented.\n"; exit; } } ## English month to numberical month table %MMchr2num = ( Jan => 1, Feb => 2, Mar => 3, Apr => 4, May => 5, Jun => 6, Jul => 7, Aug => 8, Sep => 9, Oct => 10, Nov => 11, Dec => 12); ## Get Current Dir $g_dir_cur = Cwd::getcwd(); ## Get filelist from Standerd In. @files = ; foreach $file (@files){ chomp($file); my $mailfile; if($g_mode_absolute){ $mailfile = "$file"; } else { $mailfile = "${g_dir_cur}/$file"; } ## Initialize flags and parameters $flag_valid_header = undef; $flag_received_found = undef; $YYYY = undef; $MM = undef; $DD = undef; $hhmmss = undef; $offset = undef; ## Open mail file with read only mode if( -f $mailfile){ open(IN, "<", "$mailfile"); } elsif (-d $mailfile){ next; } else { print STDERR "File ${mailfile} cannot be opened.\n"; } ## Large files consume a lot of memory. ## If you know that in advance, consider the following loop method. ## foreach $line (@lines) { => while ($line = ) { ## Memo: If you choose the above loop methods, do not forget the following... ## close() moves end of loop. Delete @lines = ; @lines = ; close(IN); ## Start file execute &print_info("-----\n"); &print_info("INFO: Executing file \"$mailfile\"\n"); foreach $line (@lines) { if ($g_pref_mode_parse_headeronly){ if($line =~ /^$/){ last; } } if ( ($g_pref_mode_header == 0) || ($g_pref_mode_header == 2) ){ if($line =~ /^Received:/){ $flag_received_found = 1; } } my $check_timestamp = &extract_timestamp($line); if ( ($check_timestamp == undef ) || ($check_timestamp == 0) ){ next; }else{ ## This file has valid header $flag_valid_header = 1; ## Crop Unnecessary strings. if ($line =~ /^Date:.+/){ ## Date header $input = (split(/Date:/,$line))[1]; } elsif ($line =~ /;/){ ## Timestamp in Received header usually include ";". $input = (split(/;/,$line))[1]; } else { $input = $line; } if ($g_mode_debug){ print STDERR "DEBUG: \$line $line"; print STDERR "DEBUG: \$input $input\n"; } chomp($input); ### Start Parse ## Remove Week of day if($input =~ /(Mon|Tue|Wed|Th[ur]|Fri|Sat|Sun)/){ $input =~ s/$1//eg; } ## Extract Year if($input =~ /([1-9][0-9]{3})/){ $YYYY = $1; $input =~ s/$1//eg; } ## Extract timestamp of hh:mm:ss if($input =~ /([0-2][0-9]:[0-5][0-9]:[0-5][0-9])/){ $hhmmss = $1; $input =~ s/$1//eg; } ## Extract month. if($input =~ /([JFMASOND][aepuco][nbrylgptvc])/){ $MM = $MMchr2num{"$1"}; $input =~ s/$1//eg; } ## Extract Timezone(Hours difference between UTC and localtime) if($input =~ /([+-][01][0-9][0-5][035])/){ $header_tz_diff = $1; $input =~ tr/$1//; } ## Extract day of month if($input =~ /(0?[0-9]|[1-2][0-9]|3[0-1])/){ $DD = $1; $input =~ s/$1//eg; } ## Get UNIX epoch sec from header timestamp my $header_year = $YYYY - 1900; my $header_month = $MM - 1; my $header_day = $DD; my ($header_hour, $header_min, $header_sec) = split(/:/, $hhmmss); my $header_epoch_sec_local = timelocal($header_sec, $header_min, $header_hour, $header_day, $header_month, $header_year); my $header_timestamp = sprintf("%04d-%02d-%02d %s%s",$YYYY, $MM, $DD, $hhmmss, $header_tz_diff); ## Get hour and minutes from TZ value "+/-hhmm" (Differnce between UTC and Localtime) my $header_tz_diff_symbol = substr($header_tz_diff, 0, 1); # + in +0900 my $header_tz_diff_hours = substr($header_tz_diff, 1, 2); # 09 in +0900 my $header_tz_diff_minutes = substr($header_tz_diff, 3, 2); # 00 in +0900 ## Invert TZ offset symbol change if( ($header_tz_diff_symbol eq "-" ) ){ $header_tz_diff_symbol = +1; } else { $header_tz_diff_symbol = -1; } ## Get TZ seconds difference of between UTC and localtime. $header_tz_diff_sec = $header_tz_diff_symbol * $header_tz_diff_hours * 3600 + $header_tz_diff_minutes * 60; ## Get UTC from header time stamp $header_epoch_sec_utc = $header_epoch_sec_local + $header_tz_diff_sec; ## Final destination arrvied time $arrived_epoch_sec = $header_epoch_sec_utc + $g_pref_tz_sec; if($g_mode_debug){ ## Header local time my ($s, $mi, $h, $d, $mo, $y) = localtime($header_epoch_sec_local); $y += 1900; $mo++; $test_reconv_local = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $y, $mo, $d, $h, $mi, $s); ## Header UTC time my ($s, $mi, $h, $d, $mo, $y) = localtime($header_epoch_sec_utc); $y += 1900; $mo++; $test_reconv_utc = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $y, $mo, $d, $h, $mi, $s); } ## Final destination arrived local time my ($s, $mi, $h, $d, $mo, $y) = localtime($arrived_epoch_sec); $y += 1900; $mo++; $test_reconv_arrival = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $y, $mo, $d, $h, $mi, $s); ## Make arguments of touch -d command. $touch_mod_timestamp = sprintf("%04d-%02d-%02dT%02d:%02d:%02d", $y, $mo, $d, $h, $mi, $s); ## Output if($g_mode_debug){ print "DEBUG: LocalTime(Header_Read):A=$header_timestamp\n"; print "DEBUG: Localtime_Epoch(Header_Read):B=timelocal(A)=${header_epoch_sec_local} localtime(timelocal(B)): $test_reconv_local Inverted_TZ_sec:${header_tz_diff_sec}\n"; print "DEBUG: UTC_Epoch(Header_Read):C=B+Inverted_TZ_sec=${header_epoch_sec_local}+(${header_tz_diff_sec})=${header_epoch_sec_utc} UTC(Header_Read)=timelocal(C):D=${test_reconv_utc}\n"; print "DEBUG: LocalTime_Epoch(Final_Dest):D=(C)+\$g_pref_tz_sec=${header_epoch_sec_utc}+(${g_pref_tz_sec})=$arrived_epoch_sec LocalTime(Final_Dest)=timelocal(D):$test_reconv_arrival\n"; } else { &print_info("INFO: Header read: $header_timestamp -> Final destinaiton time: $touch_mod_timestamp \n"); } ## Make shell command $exec_shell_touch = "$g_path_prog_touch ${touch_mod_timestamp} \"${mailfile}\" >> /dev/null"; if ($g_mode_commit){ my $exec_shell_touch_return = `$exec_shell_touch`; my $exec_shell_touch_reuslt = `$g_path_prog_echo $?`; chomp($exec_shell_touch_reuslt); if ($exec_shell_touch_reuslt == 0){ &print_info("INFO: Touch success : \"${mailfile}\"\n"); } else { print "ERROR: Touch failed : \"${mailfile}\"\n"; } } else { print "$exec_shell_touch\n"; } ### Last: Matched last; } } ## After reached EOF if( $flag_valid_header eq undef ){ print "ERROR: File \"${mailfile}\" has no valid hedear.\n"; } &print_info("-----\n"); } sub print_info { my $msg = $_[0]; if (!$g_mode_noinfo){ print $msg; } } sub print_debug { my $msg = $_[0]; if ($g_mode_debug){ print $msg; } } sub show_usage { print <<"EOSU"; Timstamp parser for touch command application. Version: ${g_version} \$ ./dateparse.pl [-h|--usage|--help] -h Show usage --usage Show usage --hlep Show usage \$ cat | ./dateparse.pl [--debug] [--commit] [--noinfo] [--absolute] \$ find ./ --type f | ./dateparse.pl [--debug] [--commit] [--noinfo] [--absolute] --dubug Show DEBUG: messages. --commit Run touch -d command internally to change file timestamp. If omit, show command only. --noinfo Don't show INFO: messages. --absolute path is written Full path. If omit, add current directory path to as prefix. Copyright 2022 ${g_auther_name} ${g_auther_url} EOSU } sub extract_timestamp { my $l = $_[0]; if ($g_pref_mode_header == 0) { ## Received: header timestamp match if ( ($l !~ /^Date:.+/) && ($l =~ /[1-9][0-9]{3}/) && ($l =~ /[0-2][0-9]:[0-5][0-9]:[0-5][0-9]/) ){ if ($flag_received_found){ return 1; } else { return 0; } } else { return 0; } } elsif ($g_pref_mode_header == 1 ) { ## Date: header timestamp match if ($l =~ /^Date:.+/){ return 1; } else { return 0; } } elsif ($g_pref_mode_header == 2) { ## Use both. if ( ($l !~ /^Date:.+/) && ($l =~ /[1-9][0-9]{3}/) && ($l =~ /[0-2][0-9]:[0-5][0-9]:[0-5][0-9]/) ){ if ($flag_received_found){ return 1; } else { return 0; } } elsif ($l =~ /^Date:.+/) { return 1; } else { return 0; } } else { ## Not supported parameter print STDERR "\$g_pref_mode_header = $g_pref_mode_header is not supported\n"; exit; } }