diff --git a/CHANGES b/CHANGES index 9c5781a3..dc476455 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +12 Feb 2008 - 2.5.0-rc4 +----------------------- + + * Updated code to be more portable so it builds with MS VC++ 8. + + 11 Feb 2008 - 2.5.0-rc3 ----------------------- diff --git a/apache2/Makefile.win b/apache2/Makefile.win index baae09f7..936026ea 100644 --- a/apache2/Makefile.win +++ b/apache2/Makefile.win @@ -1,42 +1,62 @@ - -# Path to Apache installation -BASE = "C:/Program Files/Apache Group/Apache2/" - -CC = cl - -DEFS = /nologo /Od /LD /W3 -DWIN32 -DWINNT -DLL = mod_security2.dll - -# Path to the headers - configure the libxml2 include path -INCLUDES = -I. -I$(BASE)\include -IC:\libxml2\include - -CFLAGS= -O $(INCLUDES) $(DEFS) - -# Paths to the required libraries -# Use the line below if you want to link against libxml2 -# LIBS = $(BASE)\lib\libhttpd.lib $(BASE)\lib\libapr.lib $(BASE)\lib\libaprutil.lib $(BASE)\lib\pcre.lib C:\libxml2\lib\libxml2.lib - -# Use the line belof if you don't want to link against libxml2 -LIBS = $(BASE)\lib\libhttpd.lib $(BASE)\lib\libapr.lib $(BASE)\lib\libaprutil.lib $(BASE)\lib\pcre.lib - -OBJS = mod_security2.obj apache2_config.obj apache2_io.obj apache2_util.obj \ - re.obj re_operators.obj re_actions.obj re_tfns.obj re_variables.obj \ - msc_logging.obj msc_xml.obj msc_multipart.obj modsecurity.obj msc_parsers.obj \ - msc_util.obj msc_pcre.obj persist_dbm.obj msc_reqbody.obj pdf_protect.obj \ - msc_geo.obj acmp.obj - -all: $(DLL) - -dll: $(DLL) - -.c.obj: - $(CC) $(CFLAGS) -c $< -Fo$@ - -.cpp.obj: - $(CC) $(CFLAGS) -c $< -Fo$@ - -$(DLL): $(OBJS) - $(CC) $(CFLAGS) -LD $(OBJS) -Fe$(DLL) $(LIBS) /link /NODEFAULTLIB:MSVCRT - -clean: - del $(OBJS) *.dll *.lib *.pdb *.idb *.ilk *.exp *.res *.rc *.bin +########################################################################### +### You Will need to modify the following variables for your system +########################################################################### +########################################################################### + +# Path to Apache httpd installation +BASE = C:\Apache2 + +# Path to required libraries +LIBXML2 = C:\work\libxml2-2.6.31 +LUA = C:\work\lua-5.1.3\src +PCRE = C:\work\httpd-2.2.8\srclib\pcre + +########################################################################### +########################################################################### + +CC = cL + +DEFS = /nologo /O2 /LD /W3 /wd4244 -DWIN32 -DWINNT -Dinline=APR_INLINE + +DLL = mod_security2.so + +INCLUDES = -I. -I$(PCRE) -I$(LIBXML2)\include -I$(LUA) -I$(BASE)\include + +CFLAGS= -MD $(INCLUDES) $(DEFS) + +LIBS = $(BASE)\lib\libhttpd.lib \ + $(BASE)\lib\libapr-1.lib \ + $(BASE)\lib\libaprutil-1.lib \ + $(PCRE)\LibR\pcre.lib \ + $(LIBXML2)\win32\bin.msvc\libxml2.lib \ + $(LUA)\lua.lib \ + wsock32.lib + +OBJS = mod_security2.obj apache2_config.obj apache2_io.obj apache2_util.obj \ + re.obj re_operators.obj re_actions.obj re_tfns.obj re_variables.obj \ + msc_logging.obj msc_xml.obj msc_multipart.obj modsecurity.obj \ + msc_parsers.obj msc_util.obj msc_pcre.obj persist_dbm.obj \ + msc_reqbody.obj pdf_protect.obj msc_geo.obj acmp.obj msc_lua.obj + +all: $(DLL) + +dll: $(DLL) + +mod_security2_config.h: mod_security2_config.hw + @echo off + type mod_security2_config.hw > mod_security2_config.h + +.c.obj: + $(CC) $(CFLAGS) -c $< -Fo$@ + +.cpp.obj: + $(CC) $(CFLAGS) -c $< -Fo$@ + +$(DLL): mod_security2_config.h $(OBJS) + $(CC) $(CFLAGS) -LD $(OBJS) -Fe$(DLL) $(LIBS) /link + +install: $(DLL) + copy $(DLL) $(BASE)\modules + +clean: + del $(OBJS) *.dll *.lib *.pdb *.idb *.ilk *.exp *.res *.rc *.bin mod_security2_config.h diff --git a/apache2/acmp.c b/apache2/acmp.c index 0a9782c4..cd8646dd 100644 --- a/apache2/acmp.c +++ b/apache2/acmp.c @@ -205,12 +205,15 @@ static acmp_node_t *acmp_child_for_code(acmp_node_t *parent_node, acmp_utf8_char * Adds node to parent node, if it is not already there */ static void acmp_add_node_to_parent(acmp_node_t *parent, acmp_node_t *child) { + acmp_node_t *node = NULL; + child->parent = parent; if (parent->child == NULL) { parent->child = child; return; } - acmp_node_t *node = parent->child; + + node = parent->child; for (;;) { if (node == child) return; if (node->sibling == NULL) { @@ -241,6 +244,7 @@ static void acmp_copy_nodes_recursive(acmp_node_t *from, acmp_node_t *to, apr_po acmp_node_t *old_node = from->child, *new_node, *nn2; if (old_node == NULL) return; nn2 = apr_pcalloc(pool, sizeof(acmp_node_t)); + /* ENH: Check alloc succeded */ acmp_clone_node_no_state(old_node, nn2); nn2->parent = to; to->child = nn2; @@ -250,6 +254,7 @@ static void acmp_copy_nodes_recursive(acmp_node_t *from, acmp_node_t *to, apr_po old_node = old_node->sibling; if (old_node == NULL) break; new_node = apr_pcalloc(pool, sizeof(acmp_node_t)); + /* ENH: Check alloc succeded */ acmp_clone_node_no_state(old_node, new_node); new_node->parent = to; nn2->sibling = new_node; @@ -310,6 +315,7 @@ static void acmp_add_btree_leaves(acmp_btree_node_t *node, acmp_node_t *nodes[], if ((pos - lb) > 1) { left = lb + (pos - lb) / 2; node->left = apr_pcalloc(pool, sizeof(acmp_btree_node_t)); + /* ENH: Check alloc succeded */ node->left->node = nodes[left]; node->left->letter = nodes[left]->letter; #ifdef DEBUG_ACMP @@ -319,6 +325,7 @@ static void acmp_add_btree_leaves(acmp_btree_node_t *node, acmp_node_t *nodes[], if ((rb - pos) > 1) { right = pos + (rb - pos) / 2; node->right = apr_pcalloc(pool, sizeof(acmp_btree_node_t)); + /* ENH: Check alloc succeded */ node->right->node = nodes[right]; node->right->letter = nodes[right]->letter; #ifdef DEBUG_ACMP @@ -339,25 +346,36 @@ static void acmp_add_btree_leaves(acmp_btree_node_t *node, acmp_node_t *nodes[], static void acmp_build_binary_tree(ACMP *parser, acmp_node_t *node) { apr_size_t count, i, j; acmp_node_t *child = node->child; + acmp_node_t **nodes; + apr_size_t pos; + /* Build an array big enough */ for (count = 0; child != NULL; child = child->sibling) count++; - acmp_node_t *nodes[count]; + nodes = apr_pcalloc(parser->pool, count * sizeof(acmp_node_t *)); + /* ENH: Check alloc succeded */ + + /* ENH: Combine this in the loop below - we do not need two loops */ child = node->child; for (i = 0; i < count; i++) { nodes[i] = child; child = child->sibling; }; + /* We have array with all children of the node and number of those children */ for (i = 0; i < count - 1; i++) for (j = i + 1; j < count; j++) { + acmp_node_t *tmp; + if (nodes[i]->letter < nodes[j]->letter) continue; - acmp_node_t *tmp = nodes[i]; + + tmp = nodes[i]; nodes[i] = nodes[j]; nodes[j] = tmp; } node->btree = apr_pcalloc(parser->pool, sizeof(acmp_btree_node_t)); - apr_size_t pos = count / 2; + /* ENH: Check alloc succeded */ + pos = count / 2; node->btree->node = nodes[pos]; node->btree->letter = nodes[pos]->letter; acmp_add_btree_leaves(node->btree, nodes, pos, -1, count, parser->pool); @@ -371,10 +389,11 @@ static void acmp_build_binary_tree(ACMP *parser, acmp_node_t *node) { */ static apr_status_t acmp_connect_fail_branches(ACMP *parser) { /* Already connected ? */ - if (parser->is_failtree_done != 0) return APR_SUCCESS; acmp_node_t *child, *node, *goto_node; apr_array_header_t *arr, *arr2, *tmp; + if (parser->is_failtree_done != 0) return APR_SUCCESS; + parser->root_node->text = ""; arr = apr_array_make(parser->pool, 32, sizeof(acmp_node_t *)); arr2 = apr_array_make(parser->pool, 32, sizeof(acmp_node_t *)); @@ -456,15 +475,19 @@ static void acmp_found(ACMP *parser, acmp_node_t *node) { ACMP *acmp_create(int flags, apr_pool_t *pool) { apr_status_t rc; apr_pool_t *p; + ACMP *parser; + rc = apr_pool_create(&p, pool); if (rc != APR_SUCCESS) return NULL; - ACMP *parser = apr_pcalloc(p, sizeof(ACMP)); + parser = apr_pcalloc(p, sizeof(ACMP)); + /* ENH: Check alloc succeded */ parser->pool = p; parser->parent_pool = pool; parser->is_utf8 = (flags & ACMP_FLAG_UTF8) == 0 ? 0 : 1; parser->is_case_sensitive = (flags & ACMP_FLAG_CASE_SENSITIVE) == 0 ? 0 : 1; parser->root_node = apr_pcalloc(p, sizeof(acmp_node_t)); + /* ENH: Check alloc succeded */ return parser; } @@ -487,17 +510,20 @@ void acmp_destroy(ACMP *parser) { ACMP *acmp_duplicate(ACMP *parser, apr_pool_t *pool) { apr_status_t rc; apr_pool_t *p; + ACMP *new_parser; if (pool == NULL) pool = parser->parent_pool; rc = apr_pool_create(&p, pool); if (rc != APR_SUCCESS) return NULL; - ACMP *new_parser = apr_pcalloc(p, sizeof(ACMP)); + new_parser = apr_pcalloc(p, sizeof(ACMP)); + /* ENH: Check alloc succeded */ new_parser->pool = p; new_parser->parent_pool = pool; new_parser->is_utf8 = parser->is_utf8; new_parser->is_case_sensitive = parser->is_case_sensitive; new_parser->root_node = apr_pcalloc(p, sizeof(acmp_node_t)); + /* ENH: Check alloc succeded */ new_parser->dict_count = parser->dict_count; new_parser->longest_entry = parser->longest_entry; acmp_copy_nodes_recursive(parser->root_node, new_parser->root_node, new_parser->pool); @@ -509,11 +535,15 @@ ACMP *acmp_duplicate(ACMP *parser, apr_pool_t *pool) { * Creates fail tree and initializes buffer */ apr_status_t acmp_prepare(ACMP *parser) { + apr_status_t st; + if (parser->bp_buff_len < parser->longest_entry) { parser->bp_buff_len = parser->longest_entry * 2; parser->bp_buffer = apr_pcalloc(parser->pool, sizeof(apr_size_t) * parser->bp_buff_len); + /* ENH: Check alloc succeded */ } - apr_status_t st = acmp_connect_fail_branches(parser); + + st = acmp_connect_fail_branches(parser); parser->active_node = parser->root_node; if (st != APR_SUCCESS) return st; parser->is_active = 1; @@ -532,12 +562,17 @@ apr_status_t acmp_prepare(ACMP *parser) { apr_status_t acmp_add_pattern(ACMP *parser, const char *pattern, acmp_callback_t callback, void *data, apr_size_t len) { - if (parser->is_active != 0) return APR_EGENERAL; - size_t length = (len == 0) ? acmp_strlen(parser, pattern) : len; - size_t i, j; - acmp_utf8_char_t ucs_chars[length]; + size_t length, i, j; + acmp_utf8_char_t *ucs_chars; + acmp_node_t *parent, *child; - acmp_node_t *parent = parser->root_node, *child; + if (parser->is_active != 0) return APR_EGENERAL; + + length = (len == 0) ? acmp_strlen(parser, pattern) : len; + ucs_chars = apr_pcalloc(parser->pool, length * sizeof(acmp_utf8_char_t)); + /* ENH: Check alloc succeded */ + + parent = parser->root_node; acmp_strtoucs(parser, pattern, ucs_chars, length); for (i = 0; i < length; i++) { @@ -548,10 +583,12 @@ apr_status_t acmp_add_pattern(ACMP *parser, const char *pattern, child = acmp_child_for_code(parent, letter); if (child == NULL) { child = apr_pcalloc(parser->pool, sizeof(acmp_node_t)); + /* ENH: Check alloc succeded */ child->pattern = ""; child->letter = letter; child->depth = i; child->text = apr_pcalloc(parser->pool, strlen(pattern) + 2); + /* ENH: Check alloc succeded */ for (j = 0; j <= i; j++) child->text[j] = pattern[j]; } if (i == length - 1) { @@ -559,6 +596,7 @@ apr_status_t acmp_add_pattern(ACMP *parser, const char *pattern, parser->dict_count++; child->is_last = 1; child->pattern = apr_pcalloc(parser->pool, strlen(pattern) + 2); + /* ENH: Check alloc succeded */ strcpy(child->pattern, pattern); } child->callback = callback; @@ -579,14 +617,19 @@ apr_status_t acmp_add_pattern(ACMP *parser, const char *pattern, * len - size of data in bytes */ apr_status_t acmp_process(ACMP *parser, const char *data, apr_size_t len) { - if (parser->is_failtree_done == 0) acmp_prepare(parser); - acmp_node_t *node = parser->active_node, *go_to; + acmp_node_t *node, *go_to; apr_size_t seq_length; - const char *end = (data + len); + const char *end; + + if (parser->is_failtree_done == 0) acmp_prepare(parser); + + node = parser->active_node; + end = data + len; while (data < end) { - parser->bp_buffer[parser->char_pos % parser->bp_buff_len] = parser->byte_pos; acmp_utf8_char_t letter; + + parser->bp_buffer[parser->char_pos % parser->bp_buff_len] = parser->byte_pos; if (parser->is_utf8) { if (parser->u8buff_len > 0) { /* Resuming partial utf-8 sequence */ @@ -674,6 +717,7 @@ void acmp_reset(ACMP *parser) { ACMPT *acmp_duplicate_quick(ACMP *parser, apr_pool_t *pool) { apr_pool_t *p = (pool != NULL) ? pool : parser->pool; ACMPT *dup = apr_pcalloc(p, sizeof(ACMPT)); + /* ENH: Check alloc succeded */ dup->parser = parser; return dup; } @@ -682,13 +726,18 @@ ACMPT *acmp_duplicate_quick(ACMP *parser, apr_pool_t *pool) { * Process the data using ACMPT to keep state, and ACMPT's parser to keep the tree */ apr_status_t acmp_process_quick(ACMPT *acmpt, const char **match, const char *data, apr_size_t len) { + ACMP *parser; + acmp_node_t *node, *go_to; + const char *end; + if (acmpt->parser->is_failtree_done == 0) { acmp_prepare(acmpt->parser); }; - ACMP *parser = acmpt->parser; + + parser = acmpt->parser; if (acmpt->ptr == NULL) acmpt->ptr = parser->root_node; - acmp_node_t *node = acmpt->ptr, *go_to; - const char *end = (data + len); + node = acmpt->ptr; + end = data + len; while (data < end) { acmp_utf8_char_t letter = (unsigned char)*data++; diff --git a/apache2/mod_security2_config.hw b/apache2/mod_security2_config.hw new file mode 100644 index 00000000..c9500446 --- /dev/null +++ b/apache2/mod_security2_config.hw @@ -0,0 +1 @@ +/* This file is left empty for building on Windows. */ diff --git a/apache2/modsecurity.h b/apache2/modsecurity.h index d9083ec4..8b15300d 100644 --- a/apache2/modsecurity.h +++ b/apache2/modsecurity.h @@ -64,7 +64,7 @@ extern DSOLOCAL modsec_build_type_rec modsec_build_type[]; #define MODSEC_VERSION_MINOR "5" #define MODSEC_VERSION_MAINT "0" #define MODSEC_VERSION_TYPE "rc" -#define MODSEC_VERSION_RELEASE "3" +#define MODSEC_VERSION_RELEASE "4" #define MODULE_NAME "ModSecurity for Apache" #define MODULE_RELEASE \ diff --git a/apache2/msc_lua.c b/apache2/msc_lua.c index 4d6478f6..a219b7d1 100644 --- a/apache2/msc_lua.c +++ b/apache2/msc_lua.c @@ -27,6 +27,7 @@ typedef struct { */ static const char* dump_reader(lua_State* L, void* user_data, size_t* size) { msc_lua_dumpr_t *dumpr = (msc_lua_dumpr_t *)user_data; + msc_script_part *part; /* Do we have more chunks to return? */ if (dumpr->index == dumpr->script->parts->nelts) { @@ -34,7 +35,7 @@ static const char* dump_reader(lua_State* L, void* user_data, size_t* size) { } /* Get one chunk. */ - msc_script_part *part = ((msc_script_part **)dumpr->script->parts->elts)[dumpr->index]; + part = ((msc_script_part **)dumpr->script->parts->elts)[dumpr->index]; *size = part->len; dumpr->index++; @@ -47,9 +48,10 @@ static const char* dump_reader(lua_State* L, void* user_data, size_t* size) { */ static int dump_writer(lua_State *L, const void* data, size_t len, void* user_data) { msc_lua_dumpw_t *dump = (msc_lua_dumpw_t *)user_data; + msc_script_part *part; /* Allocate new part, copy the data into it. */ - msc_script_part *part = apr_palloc(dump->pool, sizeof(msc_script_part)); + part = apr_palloc(dump->pool, sizeof(msc_script_part)); part->data = apr_palloc(dump->pool, len); part->len = len; memcpy((void *)part->data, data, len); @@ -197,6 +199,7 @@ static int l_getvar(lua_State *L) { char *p1 = NULL; apr_array_header_t *tfn_arr = NULL; msre_var *vx = NULL; + msre_var *var; /* Retrieve parameters. */ p1 = (char *)luaL_checkstring(L, 1); @@ -218,7 +221,7 @@ static int l_getvar(lua_State *L) { } /* Resolve variable. */ - msre_var *var = msre_create_var_ex(msr->msc_rule_mptmp, msr->modsecurity->msre, + var = msre_create_var_ex(msr->msc_rule_mptmp, msr->modsecurity->msre, varname, param, msr, &my_error_msg); if (var == NULL) { diff --git a/apache2/re_operators.c b/apache2/re_operators.c index ad152ccb..494b2449 100644 --- a/apache2/re_operators.c +++ b/apache2/re_operators.c @@ -194,16 +194,20 @@ static int msre_op_rx_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, c /* pm */ static int msre_op_pm_param_init(msre_rule *rule, char **error_msg) { + ACMP *p; + const char *phrase; + const char *next; + if ((rule->op_param == NULL)||(strlen(rule->op_param) == 0)) { *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for operator 'pm'."); return 0; /* ERROR */ } - ACMP *p = acmp_create(0, rule->ruleset->mp); + p = acmp_create(0, rule->ruleset->mp); if (p == NULL) return 0; - const char *phrase = apr_pstrdup(rule->ruleset->mp, rule->op_param); - const char *next = rule->op_param + strlen(rule->op_param); + phrase = apr_pstrdup(rule->ruleset->mp, rule->op_param); + next = rule->op_param + strlen(rule->op_param); /* Loop through phrases */ /* ENH: Need to allow quoted phrases w/space */ @@ -231,13 +235,14 @@ static int msre_op_pmFromFile_param_init(msre_rule *rule, char **error_msg) { const char *rulefile_path; apr_status_t rc; apr_file_t *fd; + ACMP *p; if ((rule->op_param == NULL)||(strlen(rule->op_param) == 0)) { *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for operator 'pm'."); return 0; /* ERROR */ } - ACMP *p = acmp_create(0, rule->ruleset->mp); + p = acmp_create(0, rule->ruleset->mp); if (p == NULL) return 0; fn = apr_pstrdup(rule->ruleset->mp, rule->op_param); @@ -321,6 +326,7 @@ static int msre_op_pm_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, c const char *match = NULL; apr_status_t rc = 0; int capture; + ACMPT pt; /* Nothing to read */ if ((var->value == NULL) || (var->value_len == 0)) return 0; @@ -328,7 +334,8 @@ static int msre_op_pm_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, c /* Are we supposed to capture subexpressions? */ capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0; - ACMPT pt = {(ACMP *)rule->op_param_data, NULL}; + pt.parser = (ACMP *)rule->op_param_data; + pt.ptr = NULL; rc = acmp_process_quick(&pt, &match, var->value, var->value_len); if (rc) { diff --git a/doc/modsecurity2-apache-reference.xml b/doc/modsecurity2-apache-reference.xml index bf69aa29..17ed51cc 100644 --- a/doc/modsecurity2-apache-reference.xml +++ b/doc/modsecurity2-apache-reference.xml @@ -3,7 +3,7 @@ ModSecurity Reference Manual - Version 2.5.0-rc3 (February 11, 2008) + Version 2.5.0-rc4 (February 12, 2008) 2004-2008 @@ -313,67 +313,100 @@ url="http://www.lua.org/download.html">http://www.lua.org/download.html - - Unpack the ModSecurity archive - - - - Run the configure script to generate a Makefile. Typically no - options are needed. - - ./configure - - Options are available for more customization (use - ./configure --help for a full list), but typically - you will only need to specify the location of the - apxs command installed by Apache httpd with the - --with-apxs option. - - ./configure - --with-apxs=/path/to/httpd-2.x.y/bin/apxs - - - - Compile with: make - - - - Optionally test with: make test - - - - Optionally build the ModSecurity Log Collector with: - make mlogc - - Stop Apache httpd - Optionally install mlogc: Review the - INSTALL file included in the apache2/mlogc-src - directory in the distribution. + Unpack the ModSecurity archive - Install the ModSecurity module with: make - install + Building differs for UNIX (or UNIX-like) operating systems and + Windows. + + + + UNIX + + + + Run the configure script to generate a Makefile. + Typically no options are needed. + + ./configure + + Options are available for more customization (use + ./configure --help for a full list), but + typically you will only need to specify the location of the + apxs command installed by Apache httpd with + the --with-apxs option. + + ./configure + --with-apxs=/path/to/httpd-2.x.y/bin/apxs + + + + Compile with: make + + + + Optionally test with: make + test + + + + Optionally build the ModSecurity Log Collector with: + make mlogc + + + + Optionally install mlogc: Review the + INSTALL file included in the + apache2/mlogc-src directory in the distribution. + + + + Install the ModSecurity module with: make + install + + + + + + Windows (MS VC++ 8) + + + + Edit Makefile.win to configure the + Apache base and library paths. + + + + Compile with: nmake -f + Makefile.win + + + + Install the ModSecurity module with: nmake -f + Makefile.win install + + + + - Load libxml2 before ModSecurity: LoadFile - /usr/lib/libxml2.so - + Edit the main Apache httpd config file (usually + httpd.conf) - - Load Lua before ModSecurity: LoadFile - /usr/lib/liblua5.1.so. - + On UNIX you must load libxml2 and lua before ModSecurity with + something like this: - - Load ModSecurity itself: LoadModule security2_module - modules/mod_security2.so + LoadFile /usr/lib/libxml2.so +LoadFile /usr/lib/liblua5.1.so + + Load the ModSecurity module with:LoadModule security2_module modules/mod_security2.so @@ -385,7 +418,7 @@ - You now have ModSecurity 2.x up and running. + You should now have ModSecurity 2.x up and running. @@ -5732,4 +5765,4 @@ Server: Apache/2.x.x - + \ No newline at end of file