# $Id: http.bro,v 1.39 2002/11/29 06:48:51 vern Exp $

@load alert
@load site
@load conn

redef enum Alert += {
	HTTP_SensitiveURI,	# sensitive URI in GET/POST/HEAD
};

# HTTP processing options
global process_HTTP_replies = F &redef;
global process_HTTP_data = F &redef;
global include_HTTP_abstract = F &redef;
global log_HTTP_data = F &redef;

type http_pending_request: record {
	method: string;
	URI: string;
	log_it: bool;
};

# Eventually we will combine http_pending_request and http_message.

type http_message: record {
	initiated: bool;
	code: count;		# for HTTP reply message
	reason: string;		# for HTTP reply message
	entity_level: count;	# depth of enclosing MIME entities
	data_length: count;	# actual length of data delivered
	content_length: string;	# length specified in CONTENT-LENGTH header
	header_slot: count;	# rewrite slot at the end of headers
	abstract: string;	# data abstract
	skip_abstract: bool;	# to skip abstract for certain content types
};

type http_pending_request_stream: record {
	# Number of first pending request.
	first_pending_request: count &default = 0;

	# Total number of pending requests.
	num_pending_requests: count &default = 0;

	# Indexed from [first_pending_request ..
	#		(first_pending_request + num_pending_requests - 1)]
	requests: table[count] of http_pending_request;

	next_request: http_message; 	# the on-going request
	next_reply: http_message;	# the on-going reply

	# len_next_reply: count;	# 0 means unspecified
	# len_next_request: count;

	id: string;	# repeated from http_session_info, for convenience
};

type http_session_info: record {
	id: string;

	# Indexed by a connection's source and destination ports
	# to find the corresponding request stream.  We need to
	# manage these per-connection rather than per-session
	# because across sessions they can complete out-of-order;
	# but within a connection, they can only complete in-order.
	request_streams: table[port, port] of http_pending_request_stream;
};

global http_log = open_log_file("http") &redef;

# Indexed by source and destination address.
global http_sessions: table[addr, addr] of http_session_info;
global http_session_id = 0;

# If true, we maintain HTTP sessions across multiple connections,
# otherwise we don't (which saves some memory).
global maintain_http_sessions = T &redef;

function new_http_session(c: connection): http_session_info
	{
	local session = c$id;
	local new_id = ++http_session_id;

	local info: http_session_info;
	info$id = fmt("%%%d", new_id);

	local empty_requests: table[port, port] of http_pending_request_stream;
	info$request_streams = empty_requests;

	http_sessions[session$orig_h, session$resp_h] = info;

	print http_log, fmt("%.6f %s start %s > %s", network_time(),
				info$id, c$id$orig_h, c$id$resp_h);

	return info;
	}

function lookup_http_session(c: connection): http_session_info
	{
	local s: http_session_info;
	local id = c$id;

	if ( [id$orig_h, id$resp_h] in http_sessions )
		s = http_sessions[id$orig_h, id$resp_h];
	else
		s = new_http_session(c);

	append_addl(c, s$id);

	return s;
	}

function init_http_message(msg: http_message): any
	{
	msg$initiated = F;
	msg$code = 0;
	msg$reason = "";
	msg$entity_level = 0;
	msg$data_length = 0;
	msg$content_length = "";
	msg$header_slot = 0;
	msg$abstract = "";
	msg$skip_abstract = F;
	}

function new_http_message(): http_message
	{
	local msg: http_message;
	init_http_message(msg);
	return msg;
	}

function lookup_http_request_stream(c: connection): http_pending_request_stream
	{
	local id = c$id;
	local s = lookup_http_session(c);

	if ( [id$orig_p, id$resp_p] !in s$request_streams )
		{
		local rs: http_pending_request_stream;

		rs$first_pending_request = 1;
		rs$num_pending_requests = 0;
		rs$id = s$id;

		rs$next_request = new_http_message();
		rs$next_reply = new_http_message();

		local empty: table[count] of http_pending_request;
		rs$requests = empty;

		s$request_streams[id$orig_p, id$resp_p] = rs;

		return rs;
		}

	else
		return s$request_streams[id$orig_p, id$resp_p];
	}

function get_http_message(s: http_pending_request_stream, is_orig: bool): http_message
	{
	return is_orig ? s$next_request : s$next_reply;
	}

event connection_state_remove(c: connection)
	{
	local id = c$id;

	if ( [id$orig_h, id$resp_h] in http_sessions )
		{
		local s = http_sessions[id$orig_h, id$resp_h];

		if ( [id$orig_p, id$resp_p] in s$request_streams )
			{
			local rs = s$request_streams[id$orig_p, id$resp_p];

			### We really want to do this in sequential
			### order, not table order.
			for ( i in rs$requests )
				{
				local req = rs$requests[i];
				local msg = fmt("%s %s <no reply>", req$method, req$URI);

				if ( req$log_it )
					ALERT([$alert=HTTP_SensitiveURI,
						$conn=c,
						$msg=fmt("%s %s: %s",
							id_string(c$id), c$addl,
							msg)]);

				print http_log,
					fmt("%.6f %s %s", network_time(), rs$id, msg);
				}
			}

		if ( maintain_http_sessions )
			delete s$request_streams[id$orig_p, id$resp_p];
		else
			delete http_sessions[id$orig_h, id$resp_h];
		}
	}

# event connection_timeout(c: connection)
# 	{
# 	if ( ! maintain_http_sessions )
# 		{
# 		local id = c$id;
# 		if ( [id$orig_h, id$resp_h] in http_sessions )
# 			delete http_sessions[id$orig_h, id$resp_h];
# 		}
# 	}

# event http_stats(c: connection, stats: http_stats_rec)
# 	{
# 	if ( stats$num_requests == 0 && stats$num_replies == 0 )
# 		return;
#
# 	c$addl = fmt("%s (%d v%.1f v%.1f)", c$addl, stats$num_requests, stats$request_version, stats$reply_version);
# 	}
