#!/usr/bin/env ruby

# needed for urldecode
require 'cgi'

# this line imports the libpcap ruby bindings
require 'pcaplet'

# create a sniffer that grabs the first n bytes of each packet
$network = Pcaplet.new('-s 150000 -i eth0')

# create a filter that uses our query string and the sniffer we just made
$filter = Pcap::Filter.new('tcp and dst port 80', $network.capture)

# add the new filter to the sniffer
$network.add_filter($filter)

# a somewhat simplistic approach to connection tracking: we only track the previous connection
# since we're trying to match two packets, one right after another, and the app is not really critically important,
# we can live with the race conditions it brings
prevtun = nil
# in case there are postdata already in the first packet
postdata = ""

# for every packet captured
for p in $network
  # if the packet matches the filter
  if $filter =~ p
    method = nil
    p.tcp_data =~ /(GET|POST|HEAD)(.*)HTTP.*Host:([^\r\n]*)/xm
    # if not matching, we're probably looking at a continuation
    if $1 == nil
      # there are easier ways to serialize this; this one is somewhat human-readable
      tun = "#{p.ip_src}:#{p.tcp_sport}-#{p.ip_dst}:#{p.tcp_dport}"
      # still the same connection
      if tun == prevtun
        if postdata == nil
          postdata = ""
        end
        postdata = postdata + p.tcp_data unless p.tcp_data == nil
        
        # show post data
        postitems = postdata.split('&')
        postitems.each { |element| 
          el = CGI::unescape(element)
          puts "\t\t\t\t#{el}"
	}
	
	# we only needed the next element, no need to process them all
	# so we reset the tracking
        prevtun = nil
      end
      
    # one of the methods, has URL, and has hostname
    elsif $1 and $2 and $3
      method = $1.strip
      remain = $2.strip
      host = $3.strip
      
      # we don't care about media files
      if not remain =~ /\.(gif|jpe?g|png|css|swf|js|mpe?g|wav|flv|avi|ogg|mp.|rm|ram|qt)$/i
        path = remain.split('?')
	if path.count > 1
	  path[0] += '?'
	end
	
	# show UA
        ua_found = ""
	if method != 'POST'
          p.tcp_data =~ /User-Agent: ([^\r\n]*)/xm
          ua = $1
          ua_found = ua
          ua =~ /(Chrom[^ ]*)/i
          if $1 
            ua_found = $1
          else
            ua =~ /(MSIE [^ ;]*)/i
            if $1 
              ua_found = $1
            else
              ua =~ /(Firefox\/[^ ]*)/i
              if $1 
                ua_found = $1
              end
            end
          end
          if ua_found == nil
            ua_found = ""
          else
            ua_found = "#{ua_found} - "
          end
	end
	
	# show source - destination - UA - method - URL
        puts "#{p.src} - #{p.dst} - #{ua_found}#{method} http://#{host}#{path[0]}"
        
        # ... and exploded GET query if applicable
	if path.count > 1
          getitems = path[1].split('&')
          getitems.each { |element| 
            el = CGI::unescape(element)
            puts "\t\t\t\t#{el}"
	  }
	end

        # prepare to show POST data also	
        if method == 'POST'
          postdata = ""
          p.tcp_data =~ /\r\n\r\n(.*)/xm
          if $1 != nil
            postdata = $1
          end
          prevtun = "#{p.ip_src}:#{p.tcp_sport}-#{p.ip_dst}:#{p.tcp_dport}" unless (host == 'ocsp.thawte.com' and path[0] == '/')
          # thawte checks in Chrom*, binary data, ignore
          
        end
      end
    end
  end
end

