Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
97.37% |
37 / 38 |
|
90.00% |
9 / 10 |
CRAP | |
0.00% |
0 / 1 |
Origin | |
97.37% |
37 / 38 |
|
90.00% |
9 / 10 |
20 | |
0.00% |
0 / 1 |
parseHeaderList | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
isNullOrigin | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isMultiOrigin | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getOriginList | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSingleOrigin | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
match | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
matchSingleOrigin | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
5 | |||
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
execute | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
3.04 | |||
wildcardToRegex | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Rest\HeaderParser; |
4 | |
5 | use Wikimedia\Assert\Assert; |
6 | |
7 | /** |
8 | * A class to assist with the parsing of Origin header according to the RFC 6454 |
9 | * @link https://tools.ietf.org/html/rfc6454#section-7 |
10 | * @since 1.36 |
11 | */ |
12 | class Origin extends HeaderParserBase { |
13 | |
14 | public const HEADER_NAME = 'Origin'; |
15 | |
16 | /** @var bool whether the origin was set to null */ |
17 | private $isNullOrigin; |
18 | |
19 | /** @var array List of specified origins */ |
20 | private $origins = []; |
21 | |
22 | /** |
23 | * Parse an Origin header list as returned by RequestInterface::getHeader(). |
24 | * |
25 | * @param string[] $headerList |
26 | * @return self |
27 | */ |
28 | public static function parseHeaderList( array $headerList ): self { |
29 | $parser = new self( $headerList ); |
30 | $parser->execute(); |
31 | return $parser; |
32 | } |
33 | |
34 | /** |
35 | * Whether the Origin header was explicitly set to `null`. |
36 | * |
37 | * @return bool |
38 | */ |
39 | public function isNullOrigin(): bool { |
40 | return $this->isNullOrigin; |
41 | } |
42 | |
43 | /** |
44 | * Whether the Origin header contains multiple origins. |
45 | * |
46 | * @return bool |
47 | */ |
48 | public function isMultiOrigin(): bool { |
49 | return count( $this->getOriginList() ) > 1; |
50 | } |
51 | |
52 | /** |
53 | * Get the list of origins. |
54 | * |
55 | * @return string[] |
56 | */ |
57 | public function getOriginList(): array { |
58 | return $this->origins; |
59 | } |
60 | |
61 | /** |
62 | * @return string |
63 | */ |
64 | public function getSingleOrigin(): string { |
65 | Assert::precondition( !$this->isMultiOrigin(), |
66 | 'Cannot get single origin, header specifies multiple' ); |
67 | return $this->getOriginList()[0]; |
68 | } |
69 | |
70 | /** |
71 | * Check whether all the origins match at least one of the rules in $allowList. |
72 | * |
73 | * @param string[] $allowList |
74 | * @param string[] $excludeList |
75 | * @return bool |
76 | */ |
77 | public function match( array $allowList, array $excludeList ): bool { |
78 | if ( $this->isNullOrigin() ) { |
79 | return false; |
80 | } |
81 | |
82 | foreach ( $this->getOriginList() as $origin ) { |
83 | if ( !self::matchSingleOrigin( $origin, $allowList, $excludeList ) ) { |
84 | return false; |
85 | } |
86 | } |
87 | return true; |
88 | } |
89 | |
90 | /** |
91 | * Checks whether the origin matches at list one of the provided rules in $allowList. |
92 | * |
93 | * @param string $origin |
94 | * @param array $allowList |
95 | * @param array $excludeList |
96 | * @return bool |
97 | */ |
98 | private static function matchSingleOrigin( string $origin, array $allowList, array $excludeList ): bool { |
99 | foreach ( $allowList as $rule ) { |
100 | if ( preg_match( self::wildcardToRegex( $rule ), $origin ) ) { |
101 | // Rule matches, check exceptions |
102 | foreach ( $excludeList as $exc ) { |
103 | if ( preg_match( self::wildcardToRegex( $exc ), $origin ) ) { |
104 | return false; |
105 | } |
106 | } |
107 | |
108 | return true; |
109 | } |
110 | } |
111 | |
112 | return false; |
113 | } |
114 | |
115 | /** |
116 | * Private constructor. Use the public static functions for public access. |
117 | * |
118 | * @param string[] $input |
119 | */ |
120 | private function __construct( array $input ) { |
121 | if ( count( $input ) !== 1 ) { |
122 | $this->error( 'Only a single Origin header field allowed in HTTP request' ); |
123 | } |
124 | $this->setInput( trim( $input[0] ) ); |
125 | } |
126 | |
127 | private function execute() { |
128 | if ( $this->input === 'null' ) { |
129 | $this->isNullOrigin = true; |
130 | } else { |
131 | $this->isNullOrigin = false; |
132 | $this->origins = preg_split( '/\s+/', $this->input ); |
133 | if ( count( $this->origins ) === 0 ) { |
134 | $this->error( 'Origin header must contain at least one origin' ); |
135 | } |
136 | } |
137 | } |
138 | |
139 | /** |
140 | * Helper function to convert wildcard string into a regex |
141 | * '*' => '.*?' |
142 | * '?' => '.' |
143 | * |
144 | * @param string $wildcard String with wildcards |
145 | * @return string Regular expression |
146 | */ |
147 | private static function wildcardToRegex( $wildcard ) { |
148 | $wildcard = preg_quote( $wildcard, '/' ); |
149 | $wildcard = str_replace( |
150 | [ '\*', '\?' ], |
151 | [ '.*?', '.' ], |
152 | $wildcard |
153 | ); |
154 | |
155 | return "/^https?:\/\/$wildcard$/"; |
156 | } |
157 | } |