safecss_filter_attr()
云策文档标注
概述
safecss_filter_attr() 是 WordPress 核心函数,用于过滤内联样式属性,移除不允许的 CSS 规则,确保输出安全。它基于预定义的白名单和钩子进行验证,支持自定义 CSS 变量和函数。
关键要点
- 函数接受一个 CSS 字符串参数,返回过滤后的安全字符串,第二个参数已弃用。
- 通过 safe_style_css 过滤器定义允许的 CSS 属性列表,包括背景、边框、布局等常见属性。
- 处理 URL 和渐变数据类型,使用 wp_kses_bad_protocol() 检查协议安全性。
- 支持 CSS 自定义属性(如 --*)和函数(如 var(), calc()),通过正则表达式移除它们以进行安全测试。
- 提供 safecss_filter_attr_allow_css 过滤器,允许开发者自定义 CSS 安全性检查逻辑。
- 相关函数包括 wp_kses_no_null()、wp_allowed_protocols() 等,用于辅助清理和验证。
代码示例
// 示例:使用 safecss_filter_attr() 过滤内联样式
$css = 'background-image: url(http://example.com/image.jpg); color: red;';
$filtered_css = safecss_filter_attr($css);
// 输出过滤后的 CSS 字符串,移除不安全规则注意事项
- 第二个参数 $deprecated 自 WordPress 2.8.1 起已弃用,传入非空值会触发 _deprecated_argument() 警告。
- 函数严格依赖白名单机制,未列出的 CSS 属性将被过滤掉,开发者可通过钩子扩展列表。
- 在处理用户输入的 CSS 时,应结合其他安全函数(如 esc_attr())以防止 XSS 攻击。
原文内容
Filters an inline style attribute and removes disallowed rules.
Parameters
$cssstringrequired-
A string of CSS rules.
$deprecatedstringrequired-
Not used.
Source
function safecss_filter_attr( $css, $deprecated = '' ) {
if ( ! empty( $deprecated ) ) {
_deprecated_argument( __FUNCTION__, '2.8.1' ); // Never implemented.
}
$css = wp_kses_no_null( $css );
$css = str_replace( array( "n", "r", "t" ), '', $css );
$allowed_protocols = wp_allowed_protocols();
$css_array = explode( ';', trim( $css ) );
/**
* Filters the list of allowed CSS attributes.
*
* @since 2.8.1
*
* @param string[] $attr Array of allowed CSS attributes.
*/
$allowed_attr = apply_filters(
'safe_style_css',
array(
'background',
'background-color',
'background-image',
'background-position',
'background-repeat',
'background-size',
'background-attachment',
'background-blend-mode',
'border',
'border-radius',
'border-width',
'border-color',
'border-style',
'border-right',
'border-right-color',
'border-right-style',
'border-right-width',
'border-bottom',
'border-bottom-color',
'border-bottom-left-radius',
'border-bottom-right-radius',
'border-bottom-style',
'border-bottom-width',
'border-bottom-right-radius',
'border-bottom-left-radius',
'border-left',
'border-left-color',
'border-left-style',
'border-left-width',
'border-top',
'border-top-color',
'border-top-left-radius',
'border-top-right-radius',
'border-top-style',
'border-top-width',
'border-top-left-radius',
'border-top-right-radius',
'border-spacing',
'border-collapse',
'caption-side',
'columns',
'column-count',
'column-fill',
'column-gap',
'column-rule',
'column-span',
'column-width',
'color',
'filter',
'font',
'font-family',
'font-size',
'font-style',
'font-variant',
'font-weight',
'letter-spacing',
'line-height',
'text-align',
'text-decoration',
'text-indent',
'text-transform',
'white-space',
'height',
'min-height',
'max-height',
'width',
'min-width',
'max-width',
'margin',
'margin-right',
'margin-bottom',
'margin-left',
'margin-top',
'margin-block-start',
'margin-block-end',
'margin-inline-start',
'margin-inline-end',
'padding',
'padding-right',
'padding-bottom',
'padding-left',
'padding-top',
'padding-block-start',
'padding-block-end',
'padding-inline-start',
'padding-inline-end',
'flex',
'flex-basis',
'flex-direction',
'flex-flow',
'flex-grow',
'flex-shrink',
'flex-wrap',
'gap',
'column-gap',
'row-gap',
'grid-template-columns',
'grid-auto-columns',
'grid-column-start',
'grid-column-end',
'grid-column',
'grid-column-gap',
'grid-template-rows',
'grid-auto-rows',
'grid-row-start',
'grid-row-end',
'grid-row',
'grid-row-gap',
'grid-gap',
'justify-content',
'justify-items',
'justify-self',
'align-content',
'align-items',
'align-self',
'clear',
'cursor',
'direction',
'float',
'list-style-type',
'object-fit',
'object-position',
'opacity',
'overflow',
'vertical-align',
'writing-mode',
'position',
'top',
'right',
'bottom',
'left',
'z-index',
'box-shadow',
'aspect-ratio',
'container-type',
// Custom CSS properties.
'--*',
)
);
/*
* CSS attributes that accept URL data types.
*
* This is in accordance to the CSS spec and unrelated to
* the sub-set of supported attributes above.
*
* See: https://developer.mozilla.org/en-US/docs/Web/CSS/url
*/
$css_url_data_types = array(
'background',
'background-image',
'cursor',
'filter',
'list-style',
'list-style-image',
);
/*
* CSS attributes that accept gradient data types.
*
*/
$css_gradient_data_types = array(
'background',
'background-image',
);
if ( empty( $allowed_attr ) ) {
return $css;
}
$css = '';
foreach ( $css_array as $css_item ) {
if ( '' === $css_item ) {
continue;
}
$css_item = trim( $css_item );
$css_test_string = $css_item;
$found = false;
$url_attr = false;
$gradient_attr = false;
$is_custom_var = false;
if ( ! str_contains( $css_item, ':' ) ) {
$found = true;
} else {
$parts = explode( ':', $css_item, 2 );
$css_selector = trim( $parts[0] );
// Allow assigning values to CSS variables.
if ( in_array( '--*', $allowed_attr, true ) && preg_match( '/^--[a-zA-Z0-9-_]+$/', $css_selector ) ) {
$allowed_attr[] = $css_selector;
$is_custom_var = true;
}
if ( in_array( $css_selector, $allowed_attr, true ) ) {
$found = true;
$url_attr = in_array( $css_selector, $css_url_data_types, true );
$gradient_attr = in_array( $css_selector, $css_gradient_data_types, true );
}
if ( $is_custom_var ) {
$css_value = trim( $parts[1] );
$url_attr = str_starts_with( $css_value, 'url(' );
$gradient_attr = str_contains( $css_value, '-gradient(' );
}
}
if ( $found && $url_attr ) {
// Simplified: matches the sequence `url(*)`.
preg_match_all( '/url([^)]+)/', $parts[1], $url_matches );
foreach ( $url_matches[0] as $url_match ) {
// Clean up the URL from each of the matches above.
preg_match( '/^url(s*(['"]?)(.*)(g1)s*)$/', $url_match, $url_pieces );
if ( empty( $url_pieces[2] ) ) {
$found = false;
break;
}
$url = trim( $url_pieces[2] );
if ( empty( $url ) || wp_kses_bad_protocol( $url, $allowed_protocols ) !== $url ) {
$found = false;
break;
} else {
// Remove the whole `url(*)` bit that was matched above from the CSS.
$css_test_string = str_replace( $url_match, '', $css_test_string );
}
}
}
if ( $found && $gradient_attr ) {
$css_value = trim( $parts[1] );
if ( preg_match( '/^(repeating-)?(linear|radial|conic)-gradient(([^()]|rgb[a]?([^()]*))*)$/', $css_value ) ) {
// Remove the whole `gradient` bit that was matched above from the CSS.
$css_test_string = str_replace( $css_value, '', $css_test_string );
}
}
if ( $found ) {
/*
* Allow CSS functions like var(), calc(), etc. by removing them from the test string.
* Nested functions and parentheses are also removed, so long as the parentheses are balanced.
*/
$css_test_string = preg_replace(
'/b(?:var|calc|min|max|minmax|clamp|repeat)(((?:[^()]|(?1))*))/',
'',
$css_test_string
);
/*
* Disallow CSS containing ( & } = or comments, except for within url(), var(), calc(), etc.
* which were removed from the test string above.
*/
$allow_css = ! preg_match( '%[\(&=}]|/*%', $css_test_string );
/**
* Filters the check for unsafe CSS in `safecss_filter_attr`.
*
* Enables developers to determine whether a section of CSS should be allowed or discarded.
* By default, the value will be false if the part contains ( & } = or comments.
* Return true to allow the CSS part to be included in the output.
*
* @since 5.5.0
*
* @param bool $allow_css Whether the CSS in the test string is considered safe.
* @param string $css_test_string The CSS string to test.
*/
$allow_css = apply_filters( 'safecss_filter_attr_allow_css', $allow_css, $css_test_string );
// Only add the CSS part if it passes the regex check.
if ( $allow_css ) {
if ( '' !== $css ) {
$css .= ';';
}
$css .= $css_item;
}
}
}
return $css;
}
Hooks
- apply_filters( ‘safecss_filter_attr_allow_css’, bool $allow_css, string $css_test_string )
-
Filters the check for unsafe CSS in
safecss_filter_attr. - apply_filters( ‘safe_style_css’, string[] $attr )
-
Filters the list of allowed CSS attributes.
Changelog
| Version | Description |
|---|---|
| 6.9.0 | Added support for white-space. |
| 6.6.0 | Added support for grid-column, grid-row, and container-type. |
| 6.5.0 | Added support for background-repeat. |
| 6.4.0 | Added support for writing-mode. |
| 6.3.0 | Extended support for filter to accept a URL and added support for repeat().Added support for box-shadow. |
| 6.2.0 | Added support for aspect-ratio, position, top, right, bottom, left, and z-index CSS properties. |
| 6.1.0 | Added support for min(), max(), minmax(), clamp(), nested var() values, and assigning values to CSS variables.Added support for object-fit, gap, column-gap, row-gap, and flex-wrap.Extended margin-* and padding-* support for logical properties. |
| 5.8.0 | Added support for calc() and var() values. |
| 5.7.1 | Added support for object-position. |
| 5.3.1 | Added support for gradient backgrounds. |
| 5.3.0 | Added support for grid, flex and column layout properties.Extended background-* support for individual properties. |
| 5.2.0 | Added support for background-position and grid-template-columns. |
| 5.1.0 | Added support for text-transform. |
| 5.0.0 | Added support for background-image. |
| 4.6.0 | Added support for list-style-type. |
| 4.4.0 | Added support for min-height, max-height, min-width, and max-width. |
| 2.8.1 | Introduced. |
Skip to note 2 content
Aleksej
It is very handy for filtering css rules in functions that render the inline styles
Helper functions:
/** * Render style tag and background-image, with optional * extra css styles * @param $img_url string image url * @param $extra_css array list of css rules in key => value pair */ function prefix_bg_get_image( $img_url, $extra_css = array() ) { $css = ''; if ( ! empty( $img_url ) ) { $css .= sprintf( 'background-image: url(%s);', esc_url( $img_url ) ); } if ( $extra_css ) { $extra_css = array_map('prefix_join_css', array_keys($extra_css), array_values($extra_css)); $css .= join( '', $extra_css ); } if ( empty ( $css ) ) return; // prevent printing empty style="" attr // use css filter to clean the all combined $css return sprintf( 'style="%s"', safecss_filter_attr( $css ) ); } function prefix_render_bg_image( $img_url, $extra_css = array() ) { echo prefix_bg_get_image( $img_url, $extra_css ); } function prefix_join_css($key, $val) { return "$key: $val;"; }then just printing the inline bg image style:
// we just need to get image url, for example acf, custom meta or settings // get acf field image id $image_id = get_feild( 'background_image' ); $image_url = wp_get_attachment_image_src( $image_id ); $bg_css = array( 'background-repeat' => 'no-repeat', 'background-position' => 'center', 'background-size' => 'cover', );<!-- pass the url and styles array to render bg image --> <div class="prefix-somecalss" <?php prefix_render_bg_image( $image_url, $bg_css ); ?>></div>