diff --git a/doc b/doc
index 650a136dcc..143cf073e0 160000
--- a/doc
+++ b/doc
@@ -1 +1 @@
-Subproject commit 650a136dccefe44fa276e4fb06d9dc854f9ab06c
+Subproject commit 143cf073e0b95ddef40028027a860b9b8e5125ea
diff --git a/scripts/base/protocols/http/entities.bro b/scripts/base/protocols/http/entities.bro
index 3670d7879a..c16bb3f630 100644
--- a/scripts/base/protocols/http/entities.bro
+++ b/scripts/base/protocols/http/entities.bro
@@ -13,23 +13,45 @@ export {
filename: string &optional;
};
+ ## Maximum number of originator files to log.
+ ## :bro:see:`HTTP::max_files_policy` even is called once this
+ ## limit is reached to determine if it's enforced.
+ option max_files_orig = 15;
+
+ ## Maximum number of responder files to log.
+ ## :bro:see:`HTTP::max_files_policy` even is called once this
+ ## limit is reached to determine if it's enforced.
+ option max_files_resp = 15;
+
+ ## Called when reaching the max number of files across a given HTTP
+ ## connection according to :bro:see:`HTTP::max_files_orig`
+ ## or :bro:see:`HTTP::max_files_resp`. Break from the hook
+ ## early to signal that the file limit should not be applied.
+ global max_files_policy: hook(f: fa_file, is_orig: bool);
+
redef record Info += {
## An ordered vector of file unique IDs.
+ ## Limited to :bro:see:`HTTP::max_files_orig` entries.
orig_fuids: vector of string &log &optional;
## An ordered vector of filenames from the client.
+ ## Limited to :bro:see:`HTTP::max_files_orig` entries.
orig_filenames: vector of string &log &optional;
## An ordered vector of mime types.
+ ## Limited to :bro:see:`HTTP::max_files_orig` entries.
orig_mime_types: vector of string &log &optional;
## An ordered vector of file unique IDs.
+ ## Limited to :bro:see:`HTTP::max_files_resp` entries.
resp_fuids: vector of string &log &optional;
## An ordered vector of filenames from the server.
+ ## Limited to :bro:see:`HTTP::max_files_resp` entries.
resp_filenames: vector of string &log &optional;
## An ordered vector of mime types.
+ ## Limited to :bro:see:`HTTP::max_files_resp` entries.
resp_mime_types: vector of string &log &optional;
## The current entity.
@@ -82,6 +104,23 @@ event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priori
if ( c$http?$current_entity && c$http$current_entity?$filename )
f$info$filename = c$http$current_entity$filename;
+ local size: count;
+ local max: count;
+
+ if ( f$is_orig )
+ {
+ size = f$http?$orig_fuids ? |f$http$orig_fuids| : 0;
+ max = max_files_orig;
+ }
+ else
+ {
+ size = f$http?$resp_fuids ? |f$http$resp_fuids| : 0;
+ max = max_files_resp;
+ }
+
+ if ( size >= max && hook HTTP::max_files_policy(f, f$is_orig) )
+ return;
+
if ( f$is_orig )
{
if ( ! c$http?$orig_fuids )
@@ -125,6 +164,23 @@ event file_sniff(f: fa_file, meta: fa_metadata) &priority=5
if ( ! meta?$mime_type )
return;
+ local size: count;
+ local max: count;
+
+ if ( f$is_orig )
+ {
+ size = f$http?$orig_mime_types ? |f$http$orig_mime_types| : 0;
+ max = max_files_orig;
+ }
+ else
+ {
+ size = f$http?$resp_mime_types ? |f$http$resp_mime_types| : 0;
+ max = max_files_resp;
+ }
+
+ if ( size >= max && hook HTTP::max_files_policy(f, f$is_orig) )
+ return;
+
if ( f$is_orig )
{
if ( ! f$http?$orig_mime_types )
diff --git a/testing/btest/Baseline/plugins.hooks/output b/testing/btest/Baseline/plugins.hooks/output
index ecbb136298..4bb3902d07 100644
--- a/testing/btest/Baseline/plugins.hooks/output
+++ b/testing/btest/Baseline/plugins.hooks/output
@@ -274,7 +274,7 @@
0.000000 MetaHookPost CallFunction(Log::__create_stream, , (Weird::LOG, [columns=Weird::Info, ev=Weird::log_weird, path=weird])) ->
0.000000 MetaHookPost CallFunction(Log::__create_stream, , (X509::LOG, [columns=X509::Info, ev=X509::log_x509, path=x509])) ->
0.000000 MetaHookPost CallFunction(Log::__create_stream, , (mysql::LOG, [columns=MySQL::Info, ev=MySQL::log_mysql, path=mysql])) ->
-0.000000 MetaHookPost CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1547686218.444731, node=bro, filter=ip or not ip, init=T, success=T])) ->
+0.000000 MetaHookPost CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1551297972.277316, node=bro, filter=ip or not ip, init=T, success=T])) ->
0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Broker::LOG)) ->
0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Cluster::LOG)) ->
0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Config::LOG)) ->
@@ -459,7 +459,7 @@
0.000000 MetaHookPost CallFunction(Log::create_stream, , (Weird::LOG, [columns=Weird::Info, ev=Weird::log_weird, path=weird])) ->
0.000000 MetaHookPost CallFunction(Log::create_stream, , (X509::LOG, [columns=X509::Info, ev=X509::log_x509, path=x509])) ->
0.000000 MetaHookPost CallFunction(Log::create_stream, , (mysql::LOG, [columns=MySQL::Info, ev=MySQL::log_mysql, path=mysql])) ->
-0.000000 MetaHookPost CallFunction(Log::write, , (PacketFilter::LOG, [ts=1547686218.444731, node=bro, filter=ip or not ip, init=T, success=T])) ->
+0.000000 MetaHookPost CallFunction(Log::write, , (PacketFilter::LOG, [ts=1551297972.277316, node=bro, filter=ip or not ip, init=T, success=T])) ->
0.000000 MetaHookPost CallFunction(NetControl::check_plugins, , ()) ->
0.000000 MetaHookPost CallFunction(NetControl::init, , ()) ->
0.000000 MetaHookPost CallFunction(Notice::want_pp, , ()) ->
@@ -488,6 +488,8 @@
0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (GridFTP::skip_data, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)) ->
0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (HTTP::default_capture_password, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)) ->
0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (HTTP::http_methods, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)) ->
+0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (HTTP::max_files_orig, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)) ->
+0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (HTTP::max_files_resp, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)) ->
0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (HTTP::proxy_headers, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)) ->
0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (Input::default_mode, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)) ->
0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (Input::default_reader, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)) ->
@@ -1169,7 +1171,7 @@
0.000000 MetaHookPre CallFunction(Log::__create_stream, , (Weird::LOG, [columns=Weird::Info, ev=Weird::log_weird, path=weird]))
0.000000 MetaHookPre CallFunction(Log::__create_stream, , (X509::LOG, [columns=X509::Info, ev=X509::log_x509, path=x509]))
0.000000 MetaHookPre CallFunction(Log::__create_stream, , (mysql::LOG, [columns=MySQL::Info, ev=MySQL::log_mysql, path=mysql]))
-0.000000 MetaHookPre CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1547686218.444731, node=bro, filter=ip or not ip, init=T, success=T]))
+0.000000 MetaHookPre CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1551297972.277316, node=bro, filter=ip or not ip, init=T, success=T]))
0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Broker::LOG))
0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Cluster::LOG))
0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Config::LOG))
@@ -1354,7 +1356,7 @@
0.000000 MetaHookPre CallFunction(Log::create_stream, , (Weird::LOG, [columns=Weird::Info, ev=Weird::log_weird, path=weird]))
0.000000 MetaHookPre CallFunction(Log::create_stream, , (X509::LOG, [columns=X509::Info, ev=X509::log_x509, path=x509]))
0.000000 MetaHookPre CallFunction(Log::create_stream, , (mysql::LOG, [columns=MySQL::Info, ev=MySQL::log_mysql, path=mysql]))
-0.000000 MetaHookPre CallFunction(Log::write, , (PacketFilter::LOG, [ts=1547686218.444731, node=bro, filter=ip or not ip, init=T, success=T]))
+0.000000 MetaHookPre CallFunction(Log::write, , (PacketFilter::LOG, [ts=1551297972.277316, node=bro, filter=ip or not ip, init=T, success=T]))
0.000000 MetaHookPre CallFunction(NetControl::check_plugins, , ())
0.000000 MetaHookPre CallFunction(NetControl::init, , ())
0.000000 MetaHookPre CallFunction(Notice::want_pp, , ())
@@ -1383,6 +1385,8 @@
0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (GridFTP::skip_data, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100))
0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (HTTP::default_capture_password, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100))
0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (HTTP::http_methods, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100))
+0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (HTTP::max_files_orig, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100))
+0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (HTTP::max_files_resp, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100))
0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (HTTP::proxy_headers, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100))
0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (Input::default_mode, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100))
0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (Input::default_reader, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100))
@@ -2063,7 +2067,7 @@
0.000000 | HookCallFunction Log::__create_stream(Weird::LOG, [columns=Weird::Info, ev=Weird::log_weird, path=weird])
0.000000 | HookCallFunction Log::__create_stream(X509::LOG, [columns=X509::Info, ev=X509::log_x509, path=x509])
0.000000 | HookCallFunction Log::__create_stream(mysql::LOG, [columns=MySQL::Info, ev=MySQL::log_mysql, path=mysql])
-0.000000 | HookCallFunction Log::__write(PacketFilter::LOG, [ts=1547686218.444731, node=bro, filter=ip or not ip, init=T, success=T])
+0.000000 | HookCallFunction Log::__write(PacketFilter::LOG, [ts=1551297972.277316, node=bro, filter=ip or not ip, init=T, success=T])
0.000000 | HookCallFunction Log::add_default_filter(Broker::LOG)
0.000000 | HookCallFunction Log::add_default_filter(Cluster::LOG)
0.000000 | HookCallFunction Log::add_default_filter(Config::LOG)
@@ -2248,7 +2252,7 @@
0.000000 | HookCallFunction Log::create_stream(Weird::LOG, [columns=Weird::Info, ev=Weird::log_weird, path=weird])
0.000000 | HookCallFunction Log::create_stream(X509::LOG, [columns=X509::Info, ev=X509::log_x509, path=x509])
0.000000 | HookCallFunction Log::create_stream(mysql::LOG, [columns=MySQL::Info, ev=MySQL::log_mysql, path=mysql])
-0.000000 | HookCallFunction Log::write(PacketFilter::LOG, [ts=1547686218.444731, node=bro, filter=ip or not ip, init=T, success=T])
+0.000000 | HookCallFunction Log::write(PacketFilter::LOG, [ts=1551297972.277316, node=bro, filter=ip or not ip, init=T, success=T])
0.000000 | HookCallFunction NetControl::check_plugins()
0.000000 | HookCallFunction NetControl::init()
0.000000 | HookCallFunction Notice::want_pp()
@@ -2277,6 +2281,8 @@
0.000000 | HookCallFunction Option::set_change_handler(GridFTP::skip_data, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)
0.000000 | HookCallFunction Option::set_change_handler(HTTP::default_capture_password, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)
0.000000 | HookCallFunction Option::set_change_handler(HTTP::http_methods, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)
+0.000000 | HookCallFunction Option::set_change_handler(HTTP::max_files_orig, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)
+0.000000 | HookCallFunction Option::set_change_handler(HTTP::max_files_resp, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)
0.000000 | HookCallFunction Option::set_change_handler(HTTP::proxy_headers, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)
0.000000 | HookCallFunction Option::set_change_handler(Input::default_mode, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)
0.000000 | HookCallFunction Option::set_change_handler(Input::default_reader, Config::config_option_changed{ Config::log = (coerce [$ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value)] to Config::Info)if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, Config::log)return (Config::new_value)}, -100)
@@ -2678,7 +2684,7 @@
0.000000 | HookLoadFile base<...>/x509
0.000000 | HookLoadFile base<...>/xmpp
0.000000 | HookLogInit packet_filter 1/1 {ts (time), node (string), filter (string), init (bool), success (bool)}
-0.000000 | HookLogWrite packet_filter [ts=1547686218.444731, node=bro, filter=ip or not ip, init=T, success=T]
+0.000000 | HookLogWrite packet_filter [ts=1551297972.277316, node=bro, filter=ip or not ip, init=T, success=T]
0.000000 | HookQueueEvent NetControl::init()
0.000000 | HookQueueEvent bro_init()
0.000000 | HookQueueEvent filter_change_tracking()
diff --git a/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/http-limit-ignored.log b/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/http-limit-ignored.log
new file mode 100644
index 0000000000..652a095584
--- /dev/null
+++ b/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/http-limit-ignored.log
@@ -0,0 +1,10 @@
+#separator \x09
+#set_separator ,
+#empty_field (empty)
+#unset_field -
+#path http
+#open 2019-02-27-20-20-12
+#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p trans_depth method host uri referrer version user_agent request_body_len response_body_len status_code status_msg info_code info_msg tags username password proxied orig_fuids orig_filenames orig_mime_types resp_fuids resp_filenames resp_mime_types
+#types time string addr port addr port count string string string string string string count count count string count string set[enum] string string set[string] vector[string] vector[string] vector[string] vector[string] vector[string] vector[string]
+1369159408.455878 CHhAvVGS1DHFjwGM9 141.142.228.5 57262 54.243.88.146 80 1 POST httpbin.org /post - 1.1 curl/7.30.0 370 465 200 OK - - (empty) - - - F2yGNX2vGXLxfZeD12,Fq4rJh2kLHKa8YC1q1,F9sKY71Rb9megdy7sg - - FjeopJ2lRk9U1CNNb5 - text/json
+#close 2019-02-27-20-20-12
diff --git a/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/http-limited.log b/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/http-limited.log
new file mode 100644
index 0000000000..6429813e1e
--- /dev/null
+++ b/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/http-limited.log
@@ -0,0 +1,10 @@
+#separator \x09
+#set_separator ,
+#empty_field (empty)
+#unset_field -
+#path http
+#open 2019-02-27-20-19-27
+#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p trans_depth method host uri referrer version user_agent request_body_len response_body_len status_code status_msg info_code info_msg tags username password proxied orig_fuids orig_filenames orig_mime_types resp_fuids resp_filenames resp_mime_types
+#types time string addr port addr port count string string string string string string count count count string count string set[enum] string string set[string] vector[string] vector[string] vector[string] vector[string] vector[string] vector[string]
+1369159408.455878 CHhAvVGS1DHFjwGM9 141.142.228.5 57262 54.243.88.146 80 1 POST httpbin.org /post - 1.1 curl/7.30.0 370 465 200 OK - - (empty) - - - F2yGNX2vGXLxfZeD12 - - FjeopJ2lRk9U1CNNb5 - text/json
+#close 2019-02-27-20-19-27
diff --git a/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/http.log b/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/http.log
new file mode 100644
index 0000000000..2554b4ad7f
--- /dev/null
+++ b/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/http.log
@@ -0,0 +1,10 @@
+#separator \x09
+#set_separator ,
+#empty_field (empty)
+#unset_field -
+#path http
+#open 2019-02-27-20-18-53
+#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p trans_depth method host uri referrer version user_agent request_body_len response_body_len status_code status_msg info_code info_msg tags username password proxied orig_fuids orig_filenames orig_mime_types resp_fuids resp_filenames resp_mime_types
+#types time string addr port addr port count string string string string string string count count count string count string set[enum] string string set[string] vector[string] vector[string] vector[string] vector[string] vector[string] vector[string]
+1369159408.455878 CHhAvVGS1DHFjwGM9 141.142.228.5 57262 54.243.88.146 80 1 POST httpbin.org /post - 1.1 curl/7.30.0 370 465 200 OK - - (empty) - - - F2yGNX2vGXLxfZeD12,Fq4rJh2kLHKa8YC1q1,F9sKY71Rb9megdy7sg - - FjeopJ2lRk9U1CNNb5 - text/json
+#close 2019-02-27-20-18-53
diff --git a/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/out-limit-ignored b/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/out-limit-ignored
new file mode 100644
index 0000000000..a73a00eeca
--- /dev/null
+++ b/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/out-limit-ignored
@@ -0,0 +1,2 @@
+max_files reached
+max_files reached
diff --git a/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/out-limited b/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/out-limited
new file mode 100644
index 0000000000..a73a00eeca
--- /dev/null
+++ b/testing/btest/Baseline/scripts.base.protocols.http.multipart-file-limit/out-limited
@@ -0,0 +1,2 @@
+max_files reached
+max_files reached
diff --git a/testing/btest/scripts/base/protocols/http/multipart-file-limit.bro b/testing/btest/scripts/base/protocols/http/multipart-file-limit.bro
new file mode 100644
index 0000000000..7c0690babd
--- /dev/null
+++ b/testing/btest/scripts/base/protocols/http/multipart-file-limit.bro
@@ -0,0 +1,23 @@
+# @TEST-EXEC: bro -C -r $TRACES/http/multipart.trace
+# @TEST-EXEC: btest-diff http.log
+# @TEST-EXEC: bro -C -r $TRACES/http/multipart.trace %INPUT >out-limited
+# @TEST-EXEC: mv http.log http-limited.log
+# @TEST-EXEC: btest-diff http-limited.log
+# @TEST-EXEC: btest-diff out-limited
+# @TEST-EXEC: bro -C -r $TRACES/http/multipart.trace %INPUT ignore_http_file_limit=T >out-limit-ignored
+# @TEST-EXEC: mv http.log http-limit-ignored.log
+# @TEST-EXEC: btest-diff http-limit-ignored.log
+# @TEST-EXEC: btest-diff out-limit-ignored
+
+option ignore_http_file_limit = F;
+
+redef HTTP::max_files_orig = 1;
+redef HTTP::max_files_resp = 1;
+
+hook HTTP::max_files_policy(f: fa_file, is_orig: bool)
+ {
+ print "max_files reached";
+
+ if ( ignore_http_file_limit )
+ break;
+ }