Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
30 / 30 |
|
100.00% |
5 / 5 |
CRAP | |
100.00% |
1 / 1 |
ClientHints | |
100.00% |
30 / 30 |
|
100.00% |
5 / 5 |
13 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
onSpecialPageBeforeExecute | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
onBeforePageDisplay | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
4 | |||
getClientHintsHeaderString | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
getEmptyClientHintsHeaderString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace MediaWiki\CheckUser\HookHandler; |
4 | |
5 | use MediaWiki\Config\Config; |
6 | use MediaWiki\Hook\BeforePageDisplayHook; |
7 | use MediaWiki\SpecialPage\Hook\SpecialPageBeforeExecuteHook; |
8 | |
9 | /** |
10 | * HookHandler for entry points related to requesting User-Agent Client Hints data. |
11 | */ |
12 | class ClientHints implements SpecialPageBeforeExecuteHook, BeforePageDisplayHook { |
13 | |
14 | private Config $config; |
15 | |
16 | /** |
17 | * @param Config $config |
18 | */ |
19 | public function __construct( Config $config ) { |
20 | $this->config = $config; |
21 | } |
22 | |
23 | /** @inheritDoc */ |
24 | public function onSpecialPageBeforeExecute( $special, $subPage ) { |
25 | if ( !$this->config->get( 'CheckUserClientHintsEnabled' ) ) { |
26 | return; |
27 | } |
28 | |
29 | $request = $special->getRequest(); |
30 | if ( $request->wasPosted() ) { |
31 | // It's too late to ask for client hints when a user is POST'ing a form. |
32 | if ( $this->config->get( 'CheckUserClientHintsUnsetHeaderWhenPossible' ) ) { |
33 | $request->response()->header( $this->getEmptyClientHintsHeaderString() ); |
34 | } |
35 | return; |
36 | } |
37 | |
38 | if ( in_array( $special->getName(), $this->config->get( 'CheckUserClientHintsSpecialPages' ) ) ) { |
39 | $request->response()->header( $this->getClientHintsHeaderString() ); |
40 | } elseif ( $this->config->get( 'CheckUserClientHintsUnsetHeaderWhenPossible' ) ) { |
41 | $request->response()->header( $this->getEmptyClientHintsHeaderString() ); |
42 | } |
43 | } |
44 | |
45 | /** @inheritDoc */ |
46 | public function onBeforePageDisplay( $out, $skin ): void { |
47 | // We handle special pages in BeforeSpecialPageBeforeExecute. |
48 | if ( $out->getTitle()->isSpecialPage() || |
49 | // ClientHints is globally disabled |
50 | !$this->config->get( 'CheckUserClientHintsEnabled' ) |
51 | ) { |
52 | return; |
53 | } |
54 | |
55 | $out->addJsConfigVars( [ |
56 | // Roundabout way to ensure we have a list of values like "architecture", "bitness" |
57 | // etc for use with the client-side JS API. Make sure we get 1) just the values |
58 | // from the configuration, 2) filter out any empty entries, 3) convert to a list |
59 | 'wgCheckUserClientHintsHeadersJsApi' => array_values( array_filter( array_values( |
60 | $this->config->get( 'CheckUserClientHintsHeaders' ) |
61 | ) ) ), |
62 | ] ); |
63 | $out->addModules( 'ext.checkUser.clientHints' ); |
64 | |
65 | if ( $this->config->get( 'CheckUserClientHintsUnsetHeaderWhenPossible' ) ) { |
66 | $request = $out->getRequest(); |
67 | $request->response()->header( $this->getEmptyClientHintsHeaderString() ); |
68 | } |
69 | } |
70 | |
71 | /** |
72 | * Get the list of headers to use with Accept-CH. |
73 | * |
74 | * @return string |
75 | */ |
76 | private function getClientHintsHeaderString(): string { |
77 | $headers = implode( |
78 | ', ', |
79 | array_filter( array_keys( $this->config->get( 'CheckUserClientHintsHeaders' ) ) ) |
80 | ); |
81 | return "Accept-CH: $headers"; |
82 | } |
83 | |
84 | /** |
85 | * Get an Accept-CH header string to tell the client to stop sending client-hint data. |
86 | * |
87 | * @return string |
88 | */ |
89 | private function getEmptyClientHintsHeaderString(): string { |
90 | return "Accept-CH: "; |
91 | } |
92 | |
93 | } |