class EL_TODAYS_LOG_ENTRIES

(source code)

Client examples: NETWORK_TEST_SETTEST_HACKER_INTERCEPT_SERVLET

description

Abstraction to process log entries that match today's date

note
	description: "Abstraction to process log entries that match today's date"

	author: "Finnian Reilly"
	copyright: "Copyright (c) 2001-2022 Finnian Reilly"
	contact: "finnian at eiffel hyphen loop dot com"

	license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
	date: "2024-09-13 19:13:29 GMT (Friday 13th September 2024)"
	revision: "4"

deferred class
	EL_TODAYS_LOG_ENTRIES

inherit
	ANY

	EL_MODULE_FILE; EL_MODULE_IP_ADDRESS; EL_MODULE_TUPLE

	EL_CHARACTER_8_CONSTANTS

feature {NONE} -- Initialization

	make
		do
			log_path := default_log_path
			create relay_hacker_set.make_equal (20)
			create new_hacker_ip_list.make (10)
			create today.make_now_utc
			create time.make_now
			create internal_month_day_string.make_empty
		end

feature -- Access

	log_path: STRING

	new_hacker_ip_list: EL_ARRAYED_LIST [NATURAL]
		-- list of new IP addresses of hackers since last call to `update_hacker_ip_list'

feature -- Deferred

	default_log_path: STRING
		deferred
		end

	new_ip_number (line: STRING): NATURAL
		deferred
		ensure
			not_zero: Result > 0
		end

feature -- Element change

	update_hacker_ip_list
		-- scan tail of log with today's date to update `new_hacker_ip_list' with ip number of log entry
		-- containing any string in `warning_list'
		require
			is_log_readable: log_path = Default_log_path implies is_log_readable
		local
			day_updated: BOOLEAN; line: STRING
		do
			new_hacker_ip_list.wipe_out
			if is_new_day then
				day_updated := True
				today_compact := today.ordered_compact_date
				time_compact := 0
			end
			across new_todays_lines (month_day_string (day_updated)).query_if (agent is_new_entry) as list loop
				line := list.item
				if across warning_list as warning some line.has_substring (warning.item) end then
					check
						todays_date: new_compact_date (line, today.year) = today_compact
					end
					relay_hacker_set.put (new_ip_number (line))
					if relay_hacker_set.inserted then
						new_hacker_ip_list.extend (relay_hacker_set.found_item)
					end
				end
				if list.is_last then
					time_compact := new_compact_time (line)
				end
			end
		end

feature -- Contract Support

	is_log_readable: BOOLEAN
		-- `True' if current user has permission to read log file
		do
			Result := File.is_readable (log_path)
		end

feature {NONE} -- Implementation

	is_new_day: BOOLEAN
		-- redefine to fixed date in testing descendant
		do
			today.make_now_utc
			Result := today.ordered_compact_date > today_compact
		end

	is_new_entry (line: STRING): BOOLEAN
		do
			Result := new_compact_time (line) >= time_compact
		end

	month_day_string (day_updated: BOOLEAN): STRING
		-- fixed length date without the year
		do
			if day_updated then
				internal_month_day_string := today.formatted_out (Date_format)

			-- right justify day
				if attached internal_month_day_string as str and then str.count < Date_format.count then
					str.insert_character (' ', str.count)
				end
				internal_month_day_string.remove_head (5) -- remove year
			end
			Result := internal_month_day_string
		ensure
			year_removed_and_day_right_justified: Result.count = 6
		end

feature {NONE} -- Factory

	new_compact_date (line: STRING; year: INTEGER): INTEGER
		-- parse today from start of log line with right justified day number
		--	Oct  8 06:45:32 myching sm-mta[17403]
		local
			end_index: INTEGER
		do
			if attached Date_time_buffer.empty as date_string then
				date_string.append_integer (year)
				date_string.append_character (' ')
				end_index := line.index_of (':', 1)
				if end_index > 5 then
					date_string.append_substring (line, 1, end_index - 4)
				--	ensure canonically spaced
					date_string.replace_substring_all (Space * 2, Space * 1)
				end
				today.make_with_format (date_string, Date_format)
			end
			Result := today.ordered_compact_date
		end

	new_compact_time (line: STRING): INTEGER
		do
			if attached Date_time_buffer.copied_general (line.substring (8, 15)) as time_string then
				time.make_with_format (time_string, Time_format)
				Result := time.compact_time
			end
		end

	new_todays_lines (date_string: STRING): EL_STRING_8_LIST
		local
			log: PLAIN_TEXT_FILE
		do
			create Result.make (300)
			create log.make_open_read (log_path)
			from until log.end_of_file loop
				log.read_line
				if log.last_string.starts_with (date_string) then
					Result.extend (log.last_string.string)
				end
			end
			log.close
		end

feature {NONE} -- Deferred

	warning_list: EL_STRING_8_LIST
		deferred
		end

feature {NONE} -- Internal attributes

	internal_month_day_string: STRING

	relay_hacker_set: EL_HASH_SET [NATURAL]
		-- set of ip numbers that maybe forged and have rate limit exceeded warning

	time: EL_TIME

	time_compact: INTEGER

	today: EL_DATE

	today_compact: INTEGER

feature {NONE} -- Constants

	Date_format: STRING = "yyyy Mmm dd"

	Date_time_buffer: EL_STRING_8_BUFFER
		once
			create Result
		end

	Time_format: STRING = "[0]hh:[0]mi:[0]ss"

end