Tools 是非官方社区Wiki。社区文档正在编写中,欢迎参与。 Wiki编辑答疑群:717421103
版本250722.2
全站通知:

帮助:解析函数/css

来自WIKI实验室WIKI_BWIKI_哔哩哔哩
跳到导航 跳到搜索

css是一个解析函数。帮助:解析函数页列出了所有解析函数的说明。

css

向页面添加CSS。出自扩展 CSS

把CSS放进页面的<head>;而<bstyle>在写入位置插入。

语法

{{#css: CSS代码}}
{{#css: CSS页面名}}

行为与限制

函数返回空字符串,但会在<head>插入一个<link rel="stylesheet">

参数处理过程:

  • 如果是页面标题,加载该CSS页面,URL 参数为action=raw&ctype=text/css&css-extension=1
  • 否则若以/开头,作为服务器路径加载CSS。
  • 其它情况视为内联CSS

加载的 CSS 先经过Sanitizer::checkCss清洗,再Base64为data:text/css加入head。

清洗和安全检测要点:

  • 规范化CSS:解码字符引用与转义、清除注释、去除无效控制字符等。
  • 禁止的关键字/函数(命中任一规则,整段CSS都会被替换为/* insecure input */):
    • url
    • var
    • attr(… url …)
    • filter
    • accelerator
    • expression
    • imageimage-set
    • -o-link-o-link-source-o-replace

要用这些属性,可以使用不带检测的<bstyle>标签。

示例

{{#css: 
.a_css_demo{
  color:red;
} 
}}<span class="a_css_demo">文字</span>

效果:文字

<bstyle>/*<pre>*/
.b_css_demo{
  color:red;
} 
/*</pre>*/</bstyle><span class="b_css_demo">文字</span>

效果:文字

最佳实践

由于 #css 与 bstyle 都不会自动去重。若页面内多次使用的模板重复注入相同样式时,会造成代码冗余,甚至渲染异常,也会对搜索引擎收录不利。

因此推荐用 #var 系列函数(#vardefine / #varexist)实现“同一页面仅加载一次”(当然,被大量页面多次使用的样式应当优先放在Common.css中)。

这需要为每段样式分配一个页内唯一名称,如:CSS:Infobox:base模板导航盒CSS。写法示例:

{{#if:{{#varexists:模板破损的木棒CSS}} | |{{#vardefine:模板破损的木棒CSS|1}} {{#css:
/* css代码 */
}}}}
  • 首次调用:变量未定义 → 定义并插入CSS;之后再次调用将直接跳过。
  • 变量作用域为“页面级”,同一页面的多次经由模板/子模板转入都会生效;跨页面不会共享。

bstyle的等价写法:

{{#if:{{#varexists:模板小龙虾CSS}} || {{#vardefine:模板小龙虾CSS | 1 }}<bstyle>/*<pre>*/
/* css代码 */
/*</pre>*/</bstyle>}}
  • 由于bstyle中的内容会先作为wikitext展开后再作为css插入页面,因此需要用带注释的pre标签包裹,防止缩进和换行被解析器当作wiki语法处理。

底层代码

/** https://github.com/wikimedia/mediawiki-extensions-CSS/blob/REL1_37/CSS.class.php
 * @param Parser &$parser
 * @param string $css
 * @return string
 */
public function cssRender( Parser $parser, string $css ): string {
	$css = trim( $css );
	if ( $css === '' ) {
		return '';
	}
	$title = $this->titleFactory->newFromText( $css );
	$identifier = $this->config->get( 'CSSIdentifier' );
	$rawProtection = [ $identifier => '1' ];
	$headItem = '<!-- Begin Extension:CSS -->';

	if ( $title && $title->exists() ) {
		# Article actually in the db
		$params = [
			'action' => 'raw',
			'ctype' => 'text/css',
		] + $rawProtection;
		$url = $title->getLocalURL( $params );
		$headItem .= Html::linkedStyle( $url );
	} elseif ( $css[0] === '/' ) {
		# Regular file
		$base = $this->config->get( 'CSSPath' ) === false ?
			$this->config->get( MainConfigNames::StylePath ) :
			$this->config->get( 'CSSPath' );
		// The replacement for \ to / is to workaround a path traversal,
		// per T369486.
		// TODO: Implement a proper URL parser. There may be more niche URL
		// shenanigans one could get up to that MediaWiki's parser does not
		// handle, but which the browser does. The most surefire way to
		// guarantee that no tomfoolery happens is to 100% replicate what
		// the browser does and not only like 90% of it.
		$path = str_replace( '\\', '/', $css );
		$url = wfAppendQuery( $base . $path, $rawProtection );

		# Verify the expanded URL is still using the base URL
		$expandedUrl = $this->urlUtils->expand( $url );
		$expandedBase = $this->urlUtils->expand( $base );
		if ( $expandedUrl && $expandedBase && strpos( $expandedUrl, $expandedBase ) === 0 ) {
			$headItem .= Html::linkedStyle( $url );
		} else {
			$headItem .= '<!-- Invalid/malicious path  -->';
		}
	} else {
		# sanitized user CSS
		$css = $this->sanitizeCSS( $css );

		# Encode data URI and append link tag
		$dataPrefix = 'data:text/css;charset=UTF-8;base64,';
		$url = $dataPrefix . base64_encode( $css );

		$headItem .= Html::linkedStyle( $url );
	}

	$headItem .= '<!-- End Extension:CSS -->';
	$parser->getOutput()->addHeadItem( $headItem );
	return '';
}




/** mediawiki-1.37.0\includes\parser\Sanitizer.php
* Pick apart some CSS and check it for forbidden or unsafe structures.
* Returns a sanitized string. This sanitized string will have
* character references and escape sequences decoded and comments
* stripped (unless it is itself one valid comment, in which case the value
* will be passed through). If the input is just too evil, only a comment
* complaining about evilness will be returned.
*
* Currently URL references, 'expression', 'tps' are forbidden.
*
* NOTE: Despite the fact that character references are decoded, the
* returned string may contain character references given certain
* clever input strings. These character references must
* be escaped before the return value is embedded in HTML.
*
* @param string $value
* @return string
*/
public static function checkCss( $value ) {
	$value = self::normalizeCss( $value );

	// Reject problematic keywords and control characters
	if ( preg_match( '/[\000-\010\013\016-\037\177]/', $value ) ||
		strpos( $value, UtfNormal\Constants::UTF8_REPLACEMENT ) !== false ) {
		return '/* invalid control char */';
	} elseif ( preg_match(
		'! expression
			| filter\s*:
			| accelerator\s*:
			| -o-link\s*:
			| -o-link-source\s*:
			| -o-replace\s*:
			| url\s*\(
			| image\s*\(
			| image-set\s*\(
			| attr\s*\([^)]+[\s,]+url
			| var\s*\(
		!ix', $value ) ) {
		return '/* insecure input */';
	}
	return $value;
}

/** mediawiki-1.37.0\includes\parser\Sanitizer.php
* Normalize CSS into a format we can easily search for hostile input
*  - decode character references
*  - decode escape sequences
*  - remove comments, unless the entire value is one single comment
* @param string $value the css string
* @return string normalized css
*/
public static function normalizeCss( $value ) {
	// Decode character references like &#123;
	$value = self::decodeCharReferences( $value );

	// Decode escape sequences and line continuation
	// See the grammar in the CSS 2 spec, appendix D.
	// This has to be done AFTER decoding character references.
	// This means it isn't possible for this function to return
	// unsanitized escape sequences. It is possible to manufacture
	// input that contains character references that decode to
	// escape sequences that decode to character references, but
	// it's OK for the return value to contain character references
	// because the caller is supposed to escape those anyway.
	static $decodeRegex;
	if ( !$decodeRegex ) {
		$space = '[\\x20\\t\\r\\n\\f]';
		$nl = '(?:\\n|\\r\\n|\\r|\\f)';
		$backslash = '\\\\';
		$decodeRegex = "/ $backslash
			(?:
				($nl) |  # 1. Line continuation
				([0-9A-Fa-f]{1,6})$space? |  # 2. character number
				(.) | # 3. backslash cancelling special meaning
				() | # 4. backslash at end of string
			)/xu";
	}
	$value = preg_replace_callback( $decodeRegex,
		[ __CLASS__, 'cssDecodeCallback' ], $value );

	// Let the value through if it's nothing but a single comment, to
	// allow other functions which may reject it to pass some error
	// message through.
	if ( !preg_match( '! ^ \s* /\* [^*\\/]* \*/ \s* $ !x', $value ) ) {
		// Remove any comments; IE gets token splitting wrong
		// This must be done AFTER decoding character references and
		// escape sequences, because those steps can introduce comments
		// This step cannot introduce character references or escape
		// sequences, because it replaces comments with spaces rather
		// than removing them completely.
		$value = StringUtils::delimiterReplace( '/*', '*/', ' ', $value );

		// Remove anything after a comment-start token, to guard against
		// incorrect client implementations.
		$commentPos = strpos( $value, '/*' );
		if ( $commentPos !== false ) {
			$value = substr( $value, 0, $commentPos );
		}
	}

	return $value;
}
代码逻辑:
  • 首先判断输入的css是否是存在的页面标题或服务器存在的样式文件。如是则直接添加对应的link标签加载css
    • 首先尝试将输入的css作为标题,检测是否存在
    • 然后判断首个字符是不是 /,如果是作为服务器路径处理
  • 否则使用 sanitizeCSS 方法清理css,对css进行base64编码,再加入link标签
  • 其中,sanitizeCSS 是Mediawiki的内置方法,它会规范化CSS、并进行安全检测。如未通过,css将被替换为报错信息。
  • 规范化包括:解码字符引用、解码和处理转义序列,最后移除注释
  • 不安全的控制字符检测基于正则:/[\000-\010\013\016-\037\177]/
  • 不安全的css会被替换为/* insecure input */

实际用例

一些Wiki使用了相关特性,如下所示这个静态列表可能在下列页面更改后过时仅供批判性参考
碧蓝航线 - blhx

原神 - ys

战双帕弥什 - zspms

明日方舟 - arknights

恋与深空 - lysk

崩坏:星穹铁道 - sr

代号鸢 - yuan

赛马娘 - umamusume

第五人格 - dwrg

坎特伯雷公主与骑士唤醒冠军之剑的奇幻冒险 - gt

三国杀 - sgs

尘白禁区 - sonw

黑神话:悟空 - wukong

WIKI实验室 - tools

地下城堡2 - dxcb2

战争雷霆 - warthunder

东方归言录 - touhoulostword

卡拉彼丘 - klbq

白荆回廊 - bjhl

女神转生 - persona

赛尔计划 - seerplan

幻塔 - ht

雷索纳斯 - resonance

绝区零 - zzz

重返未来:1999 - reverse1999

偶像大师灰姑娘女孩 - imascg

公主连结 - pcr

ミナシゴノシゴト - mnsg

无期迷途 - wqmt

奇迹暖暖 - qjnn

赛尔号 - seer

地下城堡3 - dxcb3

红警3 - redalert3

孙美琪疑案 - sunmeiqi

鸣潮 - wutheringwaves

交错战线 - crosscore

千年之旅 - elf

生死狙击2 - ssjj2

世界之外 - world

戴森球计划 - dsp

梦幻模拟战 - langrisser

StardewValley星露谷物语 - stardewvalley

赛尔号星球大战 - seerwar

觅长生 - mcs

QQ飞车手游 - qqspeed

放置江湖 - fzjh

方舟指令 - fzzl

少前2:追放 - gf2

偶像荣耀/idoly pride - idolypride

深空之眼 - dhmmr