PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 3.0.2
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v3.0.2
3.0.5 3.0.4 3.0.3 3.0.2 3.0.1 3.0.0 2.11.12 2.11.11 2.11.10 2.11.9 2.11.7 2.11.8 2.11.6 2.11.5 2.11.4 2.11.3 2.11.1 2.11.2 2.11.0 2.10.5 2.10.4 2.10.3 2.10.2 2.10.1 2.10.0 All 93 releases
← All changes | includes/class-settings.php +172 -7 3.0.1 → 3.0.2 View file →
@@ -1711,8 +1711,171 @@
1711 1711 );
1712 1712 }
1713 1713
1714 1714 /**
1715 + * The settings that hold a list of file paths, by section
1716 + *
1717 + * A path is read by what it is. sanitize_text_field() deletes every %XX octet
1718 + * and, when it finds one, collapses repeated spaces, so an exclusion written
1719 + * for a folder called foto%20x was saved as fotox and excluded nothing: the
1720 + * scan compares what is stored with the paths it finds, one by one (measured
1721 + * on 1 oct 2026 against 3.0.1). Every other list in the options is a list of
1722 + * addresses, names or extensions, where nothing meaningful is lost, and keeps
1723 + * its reader.
1724 + *
1725 + * @since 3.0.2
1726 + *
1727 + * @return array<string,string[]>
1728 + */
1729 + public static function path_list_keys() {
1730 + return array(
1731 + 'file_integrity' => array( 'excluded_paths' ),
1732 + );
1733 + }
1734 +
1735 + /**
1736 + * One path as it was typed: invalid UTF-8 out, the ends trimmed, and nothing
1737 + * else touched. Invalid UTF-8 has to go because the option would not be
1738 + * written with it.
1739 + *
1740 + * A line that has tags (or a stray <) is dropped whole, not cut. A path has
1741 + * none, and wp_strip_all_tags() would cut it at the < and leave a shorter path
1742 + * than the one written, which excludes more than anyone asked: a folder
1743 + * called a<b would exclude a. Until 3.0.1 the < was kept as an entity, which
1744 + * matched nothing, and dropping the line has the same effect.
1745 + *
1746 + * A line with a line break inside is dropped too. The textarea is read line by
1747 + * line, so only an element of a list can have one (an export edited by hand),
1748 + * and the next save of the tab would show it as two lines and store two paths:
1749 + * a, a line break and wp-content would end up excluding all of wp-content.
1750 + * 3.0.1 turned the break into a space, which matched nothing, and dropping the
1751 + * line has the same effect (fourth cross review, 1 oct 2026).
1752 + *
1753 + * @since 3.0.2
1754 + *
1755 + * @param mixed $line Entry.
1756 + * @return string
1757 + */
1758 + public static function sanitize_path_line( $line ) {
1759 + if ( ! is_scalar( $line ) ) {
1760 + return '';
1761 + }
1762 +
1763 + $line = trim( wp_check_invalid_utf8( (string) $line, true ) );
1764 +
1765 + if ( false !== strpbrk( $line, "\r\n" ) ) {
1766 + return '';
1767 + }
1768 +
1769 + return wp_strip_all_tags( $line ) === $line ? $line : '';
1770 + }
1771 +
1772 + /**
1773 + * The text of a textarea of paths, one per line, each line read as one path.
1774 + *
1775 + * Line by line, because a stray < in one line would take the lines after it
1776 + * with it if the whole text went through wp_strip_all_tags().
1777 + *
1778 + * @since 3.0.2
1779 + *
1780 + * @param mixed $text Text.
1781 + * @return string
1782 + */
1783 + public static function sanitize_path_text( $text ) {
1784 + $lines = preg_split( '/\r\n|\r|\n/', (string) $text );
1785 +
1786 + return implode( "\n", array_map( array( __CLASS__, 'sanitize_path_line' ), (array) $lines ) );
1787 + }
1788 +
1789 + /**
1790 + * Take the lists of paths out of a settings tree before the generic sanitizer reads it
1791 + *
1792 + * Known limits (fourth cross review, 1 oct 2026), none reachable from the
1793 + * settings screen and all on the side of watching more: process_section_data()
1794 + * reads the entries of a list posted as an array (excluded_paths[]=) with
1795 + * sanitize_text_field() and applies sanitize_key() to the keys, so a key
1796 + * written with another spelling is not found here, and a form posted as an
1797 + * array instead of a string goes through a generic sanitizer. In those three
1798 + * the %XX are lost, as they were in 3.0.1.
1799 + *
1800 + * @since 3.0.2
1801 + *
1802 + * @param mixed $data Settings tree: section => key => value.
1803 + * @return array Only the path lists that are present, in the same shape, each already read as paths.
1804 + */
1805 + public static function take_path_lists( $data ) {
1806 + $taken = array();
1807 +
1808 + if ( ! is_array( $data ) ) {
1809 + return $taken;
1810 + }
1811 +
1812 + foreach ( self::path_list_keys() as $section => $keys ) {
1813 + if ( ! isset( $data[ $section ] ) || ! is_array( $data[ $section ] ) ) {
1814 + continue;
1815 + }
1816 +
1817 + foreach ( $keys as $key ) {
1818 + if ( ! isset( $data[ $section ][ $key ] ) ) {
1819 + continue;
1820 + }
1821 +
1822 + $value = $data[ $section ][ $key ];
1823 +
1824 + if ( is_array( $value ) ) {
1825 + $taken[ $section ][ $key ] = array_map( array( __CLASS__, 'sanitize_path_line' ), array_values( array_filter( $value, 'is_scalar' ) ) );
1826 + } elseif ( is_scalar( $value ) ) {
1827 + $taken[ $section ][ $key ] = self::sanitize_path_text( $value );
1828 + }
1829 + }
1830 + }
1831 +
1832 + return $taken;
1833 + }
1834 +
1835 + /**
1836 + * Put back the lists of paths take_path_lists() took out, over what the generic sanitizer left
1837 + *
1838 + * @since 3.0.2
1839 + *
1840 + * @param mixed $data Sanitized settings tree.
1841 + * @param array $taken What take_path_lists() returned for the same tree.
1842 + * @return mixed The tree, with the path lists as they were read.
1843 + */
1844 + public static function restore_path_lists( $data, $taken ) {
1845 + if ( ! is_array( $data ) ) {
1846 + return $data;
1847 + }
1848 +
1849 + foreach ( $taken as $section => $values ) {
1850 + if ( ! isset( $data[ $section ] ) || ! is_array( $data[ $section ] ) ) {
1851 + continue;
1852 + }
1853 +
1854 + foreach ( $values as $key => $value ) {
1855 + $data[ $section ][ $key ] = $value;
1856 + }
1857 + }
1858 +
1859 + return $data;
1860 + }
1861 +
1862 + /**
1863 + * Whether a position in the options, as section.key, is a list of paths
1864 + *
1865 + * @since 3.0.2
1866 + *
1867 + * @param string $trail Section and key joined by a dot.
1868 + * @return bool
1869 + */
1870 + private static function is_path_list( $trail ) {
1871 + $parts = explode( '.', (string) $trail, 2 );
1872 + $paths = self::path_list_keys();
1873 +
1874 + return 2 === count( $parts ) && isset( $paths[ $parts[0] ] ) && in_array( $parts[1], $paths[ $parts[0] ], true );
1875 + }
1876 +
1877 + /**
1715 1878 * Validate options before saving
1716 1879 *
1717 1880 * @param array $input Raw input to validate.
1718 1881 * @return array Validated options.
@@ -1735,9 +1898,9 @@
1735 1898 : false;
1736 1899 }
1737 1900 } elseif ( isset( $defaults[ $section ] ) ) {
1738 1901 // Validate other sections using generic validator
1739 - $validated[ $section ] = $this->validate_section( $data, $defaults[ $section ] );
1902 + $validated[ $section ] = $this->validate_section( $data, $defaults[ $section ], $section );
1740 1903
1741 1904 // The few keys that live outside get_default_options() on
1742 1905 // purpose (see apply_install_tweaks()) survive with their own
1743 1906 // validation, or an import would silently lose them and the
@@ -1830,13 +1993,14 @@
1830 1993 * could merge any key it liked into vigilante_options (S7 of the 28 Aug
1831 1994 * 2026 audit). Lists are the exception, handled first: their entries are
1832 1995 * data, not keys.
1833 1996 *
1834 - * @param array $input Input values.
1835 - * @param array $defaults Default values.
1997 + * @param array $input Input values.
1998 + * @param array $defaults Default values.
1999 + * @param string $trail Where this is in the options, as section.key, so a list of paths is read as paths.
1836 2000 * @return array Validated values.
1837 2001 */
1838 - private function validate_section( $input, $defaults ) {
2002 + private function validate_section( $input, $defaults, $trail = '' ) {
1839 2003 $validated = array();
1840 2004
1841 2005 if ( $this->is_list_default( $defaults ) ) {
1842 2006 if ( ! is_array( $input ) ) {
@@ -1842,12 +2006,13 @@
1842 2006 if ( ! is_array( $input ) ) {
1843 2007 return array();
1844 2008 }
1845 2009
1846 - $list = array();
2010 + $is_paths = self::is_path_list( $trail );
2011 + $list = array();
1847 2012 foreach ( $input as $value ) {
1848 2013 if ( is_scalar( $value ) ) {
1849 - $list[] = sanitize_text_field( (string) $value );
2014 + $list[] = $is_paths ? self::sanitize_path_line( $value ) : sanitize_text_field( (string) $value );
1850 2015 } elseif ( is_array( $value ) ) {
1851 2016 $list[] = map_deep( $value, 'sanitize_text_field' );
1852 2017 }
1853 2018 }
@@ -1868,9 +2033,9 @@
1868 2033 } elseif ( is_int( $default_value ) ) {
1869 2034 $validated[ $key ] = intval( $value );
1870 2035 } elseif ( is_array( $default_value ) ) {
1871 2036 if ( is_array( $value ) ) {
1872 - $validated[ $key ] = $this->validate_section( $value, $default_value );
2037 + $validated[ $key ] = $this->validate_section( $value, $default_value, '' === $trail ? (string) $key : $trail . '.' . $key );
1873 2038 } else {
1874 2039 $validated[ $key ] = $default_value;
1875 2040 }
1876 2041 } else {