= File Handling functions

The file handling functions allow for a limited number of operations on files.

The filenames can be taken from `tainted` sources, in which cases special characters such as '`/`' and '`$`' are escaped.  Any special character is replaced with an underscore, followed by the hex value of the character.  Valid UTF-8 characters are allowed.

For example, the `tainted` string `user@freeradius.org/..` will turn into the filename `user@freeradius.org_2f..`.  This operation renders the filename "safe" for operations on the local file system.  It is not possible for `tainted` data to create files, or to perform directory traversal attacks.

== %file.escape(_string_)

.Return: _string_

This function returns an escaped or "safe" version of the input string.

In some cases, as when using `%exec(...)`, it is impossible to determine which arguments are filenames, and which are simple strings.  This function allows the server to safely pass in a filename to external programs.

The returned filename is guaranteed to be safe to use.  Any portion of the filename which is taken from a "safe" source (i.e. configuration files, etc. controlled by an administrator) is used as-is.  Any portion of the filename which is taken from an "unsafe" source (i.e. network apckets) is escaped.  The result is that characters like `/` in unsafe inputs cannot be used to perform directory traversal attacks.

.Safely pass a filename to `%exec(...)`
====
[source,unlang]
----
&filename = "${logdir}/" + %file.escape(%{User-Name})

%exec("/bin/rm", %{filename})
----
====


== %file.exists(_string_)

.Return: _bool_

This function returns `true` if a file exists, or `false` if the file does not exist.

.Checking if a file exists
====
[source,unlang]
----
if %file.exists("/var/log/radius.log") {
	# do things...
}
----
====

== %file.head(_string_)

.Return: _string_

This function returns the first line of the file.  If the file does not exist, or if the line is more than 256 characters in length, it fails and nothing is returned.

.Returning the first line of a file
====
[source,unlang]
----
string line

&line := %file.head("/var/log/radius.log")
----
====

== %file.rm(_string_)

.Return: _bool_

This function removes a file.  If the file exists and could be removed, it returns `true`.  Otherwise if the file does not exist, or if the file could not be removed, it returns `false.

.Removing a file
====
[source,unlang]
----
if (%file.size("/var/log/radius.log") > (((uint64)1) << 20)) {
	%file.rm("/var/log/radius.log")
}
----
====

== %file.size(_string_)

.Return: _uint64_

This function checks the size of a file.

.Checking the size of a file
====
[source,unlang]
----
if (%file.size("/var/log/radius.log") > (((uint64)1) << 20)) {
	%log.info("The log file is getting large!")
}
----
====

== %file.tail(_string_, [ _uint32_ ])

.Return: _string_

This function returns the last line of the file.  If the file does not exist, or if the line is more than 256 characters in length, it fails and nothing is returned.

The function takes an optional second argument, which is the number of lines which should be returned.  The maximum number of lines which will be returned is limited to 15.

.Returning the first line of a file
====
[source,unlang]
----
string line

&line := %file.tail("/var/log/radius.log")

&line := %file.tail("/var/log/radius.log", 2)
----
====

// Copyright (C) 2023 Network RADIUS SAS.  Licenced under CC-by-NC 4.0.
// This documentation was developed by Network RADIUS SAS.
