@@ -113,6 +113,15 @@ class Htaccess extends Root {
113113 const MARKER_START = ' start ### ' ;
114114 const MARKER_END = ' end ### ' ;
115115
116+ /**
117+ * Allowlists for values written into .htaccess directives: cookie names, `-qs:` keys, regex literals.
118+ *
119+ * @since 7.9.2
120+ */
121+ const PATTERN_COOKIE_NAME = '/^,?[A-Za-z0-9!#& \'*+\-.^_`|~]+\z/ ' ;
122+ const PATTERN_DROP_QS = '/^[A-Za-z0-9_.*\-]+\z/ ' ;
123+ const PATTERN_REGEX_LITERAL = '/^[^\x00-\x1F\x7F" \'\\\\]+\z/ ' ;
124+
116125 /**
117126 * Initialize the class and set its properties.
118127 *
@@ -152,7 +161,7 @@ public function __construct() {
152161 self ::LS_MODULE_REWRITE_START , // <IfModule mod_rewrite.c>
153162 self ::REWRITE_ON , // RewriteEngine on
154163 'RewriteRule ' . preg_quote (LITESPEED_DATA_FOLDER ) . '/debug/.*\.log$ - [F,L] ' , // phpcs:ignore WordPress.PHP.PregQuoteDelimiter.Missing
155- 'RewriteRule ' . preg_quote ( self :: CONF_FILE ) . ' - [F,L] ' , // phpcs:ignore WordPress.PHP.PregQuoteDelimiter.Missing
164+ 'RewriteRule (^|/)\.litespeed_conf\. - [F,L] ' ,
156165 self ::LS_MODULE_END , // </IfModule>
157166 ];
158167
@@ -578,6 +587,26 @@ private function _cors_rules() {
578587 );
579588 }
580589
590+ /**
591+ * Keep only the entries of a list setting that may be written into a .htaccess directive.
592+ *
593+ * @since 7.9.2
594+ * @param mixed $values Setting value.
595+ * @param string $pattern Allowlist regex an entry must match in full.
596+ * @return array
597+ */
598+ private function _htaccess_list ( $ values , $ pattern ) {
599+ $ clean = [];
600+ foreach ( (array ) $ values as $ v ) {
601+ if ( is_string ( $ v ) && preg_match ( $ pattern , trim ( $ v ) ) ) {
602+ $ clean [] = trim ( $ v );
603+ } else {
604+ self ::debug ( 'Dropped an invalid .htaccess list entry ' );
605+ }
606+ }
607+ return array_values ( array_unique ( $ clean ) );
608+ }
609+
581610 /**
582611 * Generate rewrite rules based on settings.
583612 *
@@ -588,6 +617,9 @@ private function _cors_rules() {
588617 * @return array{0:array<int,string>,1:array<int,string>,2:array<int,string>,3:array<int,string>} Rules arrays [frontend_ls, backend_ls, frontend_nonls, backend_nonls].
589618 */
590619 private function _generate_rules ( $ cfg ) {
620+ foreach ( [ Base::O_CACHE_MOBILE_RULES => self ::PATTERN_REGEX_LITERAL , Base::O_CACHE_EXC_COOKIES => self ::PATTERN_REGEX_LITERAL , Base::O_CACHE_EXC_USERAGENTS => self ::PATTERN_REGEX_LITERAL , Base::O_CACHE_DROP_QS => self ::PATTERN_DROP_QS ] as $ list_id => $ pattern ) {
621+ $ cfg [ $ list_id ] = $ this ->_htaccess_list ( isset ( $ cfg [ $ list_id ] ) ? $ cfg [ $ list_id ] : [], $ pattern );
622+ }
591623 $ new_rules = array ();
592624 $ new_rules_nonls = array ();
593625 $ new_rules_backend = array ();
@@ -644,6 +676,7 @@ private function _generate_rules( $cfg ) {
644676 }
645677 }
646678 $ vary_cookies = apply_filters ( 'litespeed_vary_cookies ' , $ vary_cookies ); // todo: test if response vary header can work in latest OLS, drop the above two lines.
679+ $ vary_cookies = $ this ->_htaccess_list ( $ vary_cookies , self ::PATTERN_COOKIE_NAME );
647680 // frontend and backend.
648681 if ( $ vary_cookies ) {
649682 $ env = 'Cache-Vary: ' . implode ( ', ' , $ vary_cookies );
0 commit comments