#!/usr/bin/env perl

#   Licensed to the Apache Software Foundation (ASF) under one
#   or more contributor license agreements.  See the NOTICE file
#   distributed with this work for additional information
#   regarding copyright ownership.  The ASF licenses this file
#   to you under the Apache License, Version 2.0 (the
#   "License"); you may not use this file except in compliance
#   with the License.  You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

# Script to decode via header
#
# @File traffic_via.pl
# @Author Meera Mosale Nataraja
#
# Usage: Use it in 2 ways
# 1. Pass Via Header with -s option \n";
#    traffic_via [-s viaheader]";
#           or
# 2. Pipe curl output
#    curl -v -H "X-Debug: Via" http://ats_server:port 2>&1| ./traffic_via.pl
#

use strict;
use warnings;
use Data::Dumper qw(Dumper);
use Getopt::Long;

my $via_header;
my $help;

#Proxy request header flags and titles
my @proxy_header_array = (
    {
        "Request headers received from client:",
        {
            'I' => "If Modified Since (IMS)",
            'C' => "cookie",
            'E' => "error in request",
            'S' => "simple request (not conditional)",
            'N' => "no-cache",
            ' ' => "unknown?",
        },
    },
   {
        "Result of Traffic Server cache lookup for URL:",
        {
                   'A' => "in cache, not acceptable (a cache \"MISS\")",
                   'H' => "in cache, fresh (a cache \"HIT\")",
                   'S' => "in cache, stale (a cache \"MISS\")",
                   'R' => "in cache, fresh Ram hit (a cache \"HIT\")",
                   'M' => "miss (a cache \"MISS\")",
                   ' ' => "unknown?",
        },
    },
    {
        "Response information received from origin server:",
        {
                    'E' => "error in response",
                    ' ' => "no server connection needed",
                    'S' => "served",
                    'N'=> "not-modified",
        }
    },
    {
        "Result of document write-to-cache:",
        {
                    'U' => "updated old cache copy",
                    'D' => "cached copy deleted",
                    'W' => "written into cache (new copy)",
                    ' ' => "no cache write performed",
        },
    },
    {
        "Proxy operation result:",
        {
                    'R' => "origin server revalidated",
                    ' ' => "unknown?",
                    'S' => "served",
                    'N' => "not-modified",
        },
    },
    {
        "Error codes (if any):",
        {
                    'A' => "authorization failure",
                    'H' => "header syntax unacceptable",
                    'C' => "connection to server failed",
                    'T' => "connection timed out",
                    'S' => "server related error",
                    'D' => "dns failure",
                    'N' => "no error",
                    'F' => "request forbidden",
        },
    },
    {
        "Tunnel info:",
        {
                    ' ' => "no tunneling",
                    'U' => "tunneling because of url (url suggests dynamic content)",
                    'M' => "tunneling due to a method (e.g. CONNECT)",
                    'O' => "tunneling because cache is turned off",
                    'F' => "tunneling due to a header field (such as presence of If-Range header)",
        },
   },
   {
        "Cache type:",
        {
                    'I' => "icp",
                    ' ' => "cache miss or no cache lookup",
                    'C' => "cache",
        },
   },
   {
        "Cache lookup result:",
        {
                    ' ' => "no cache lookup",
                    'S' => "cache hit, but expired",
                    'U' => "cache hit, but client forces revalidate (e.g. Pragma: no-cache)",
                    'D' => "cache hit, but method forces revalidated (e.g. ftp, not anonymous)",
                    'I' => "conditional miss (client sent conditional, fresh in cache, returned 412)",
                    'H' => "cache hit",
                    'M' => "cache miss (url not in cache)",
                    'C' => "cache hit, but config forces revalidate",
                    'N' => "conditional hit (client sent conditional, doc fresh in cache, returned 304)",
        },
   },
   {
        "ICP status:",
        {
                    ' ' => "no icp",
                    'S' => "connection opened successfully",
                    'F' => "connection open failed",
        },
   },
   {
        "Parent proxy connection status:",
        {
                    ' ' => "no parent proxy",
                    'S' => "connection opened successfully",
                    'F' => "connection open failed",
        },

   },
   {
        "Origin server connection status:",
        {
                    ' ' => "no server connection",
                    'S' => "connection opened successfully",
                    'F' => "connection open failed",
        },
   },
);

##Print script usage
sub usage
{
    print "\nPass Via Header with -s option \n";
    print "Usage: traffic_via [-s viaheader]";
    print "\n        or          \n";
    print "\nPipe curl command output to this program";
    print "\nEg: curl xxxx | traffic_via\n";
    print "\n-h for help\n";
    exit;
}

if (@ARGV == 0) {
    #if passed through standard input
    my @userinput = <STDIN>;
    my $via_string;

    for my $element (@userinput) {
        #Pattern matching for Via
        if ($element =~ /Via:(.*)\[(.*)\]/) {
            #Search and grep via header
            $via_string = $2;
            chomp($via_string);
            print "Via Header is [$via_string]";
            decode_via_header($via_string);
        }
    }
} else {
    usage() if (!GetOptions('s=s' => \$via_header,
                'help|?' => \$help) or
                defined $help);

    if (defined $via_header) {
        #if passed through commandline dashed argument
        print "Via Header is [$via_header]";
        decode_via_header($via_header);
    }

}

#Subroutine to decode via header
sub decode_via_header {
    my($header) = @_;
    my $hdrLength;
    my $newHeader;

    #Check via header syntax
    if ($header =~ /([a-zA-Z: ]+)/) {
        #Get via header length
        $hdrLength = length($header);

        # Valid Via header length is 24 or 6.
        # When Via header length is 24, it will have both proxy request header result and operational results.
        if ($hdrLength == 24) {
            #Split via header: proxy result and operational result
            $newHeader = join('', split(':', $header));
        } elsif ($hdrLength == 6) {
            $newHeader = $header;
        } elsif ($hdrLength == 5) {
            # When Via header length is 5, it might be missing last field. Fill it and decode header.
            my $newHeader = "$header"." ";
        } else {
            # Invalid header size, come out.
            print "\nInvalid VIA header. VIA header length should be 6 or 24 characters\n";
            return;
        }
        convert_header_to_array($newHeader);
    }


}

sub convert_header_to_array {
    my ($viaHeader) = @_;
    my @ResultArray;
    #Convert string header into character array
    while ($viaHeader =~ /(.)/g) {
            #Only capital letters indicate flags
            if ($1 !~ m/[a-z]+/) {
                push(@ResultArray, $1);
            }
    }
    print "\nVia Header details: \n";
    for (my $arrayIndex=0; $arrayIndex < scalar(@ResultArray); $arrayIndex++ ) {
        get_via_header_flags(\@proxy_header_array, $arrayIndex, $ResultArray[$arrayIndex]);
    }
}

#Get values from header arrays
sub get_via_header_flags {
    my ($arrayName, $inputIndex, $flag) = @_;

    my %flagValues;
    my @flagKeys;
    my %flags;
    my @keys;

    my @array = @$arrayName;

    %flagValues = %{$array[$inputIndex]};
    @flagKeys = keys (%flagValues);

    foreach my $keyEntry ( @flagKeys )  {
        printf ("%-55s", $keyEntry);
        %flags = %{$flagValues{$keyEntry}};
        @keys = keys (%flags);
        foreach my $key ( @keys )  {
            if ($key =~ /$flag/) {
                 #print $flags{$key};
                 printf("%s",$flags{$key});
                 print "\n";
            }
        }
    }
}
