File: Origin.php

package info (click to toggle)
mediawiki 1%3A1.43.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 417,464 kB
  • sloc: php: 1,062,949; javascript: 664,290; sql: 9,714; python: 5,458; xml: 3,489; sh: 1,131; makefile: 64
file content (157 lines) | stat: -rw-r--r-- 3,632 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
<?php

namespace MediaWiki\Rest\HeaderParser;

use Wikimedia\Assert\Assert;

/**
 * A class to assist with the parsing of Origin header according to the RFC 6454
 * @link https://tools.ietf.org/html/rfc6454#section-7
 * @since 1.36
 */
class Origin extends HeaderParserBase {

	public const HEADER_NAME = 'Origin';

	/** @var bool whether the origin was set to null */
	private $isNullOrigin;

	/** @var array List of specified origins */
	private $origins = [];

	/**
	 * Parse an Origin header list as returned by RequestInterface::getHeader().
	 *
	 * @param string[] $headerList
	 * @return self
	 */
	public static function parseHeaderList( array $headerList ): self {
		$parser = new self( $headerList );
		$parser->execute();
		return $parser;
	}

	/**
	 * Whether the Origin header was explicitly set to `null`.
	 *
	 * @return bool
	 */
	public function isNullOrigin(): bool {
		return $this->isNullOrigin;
	}

	/**
	 * Whether the Origin header contains multiple origins.
	 *
	 * @return bool
	 */
	public function isMultiOrigin(): bool {
		return count( $this->getOriginList() ) > 1;
	}

	/**
	 * Get the list of origins.
	 *
	 * @return string[]
	 */
	public function getOriginList(): array {
		return $this->origins;
	}

	/**
	 * @return string
	 */
	public function getSingleOrigin(): string {
		Assert::precondition( !$this->isMultiOrigin(),
			'Cannot get single origin, header specifies multiple' );
		return $this->getOriginList()[0];
	}

	/**
	 * Check whether all the origins match at least one of the rules in $allowList.
	 *
	 * @param string[] $allowList
	 * @param string[] $excludeList
	 * @return bool
	 */
	public function match( array $allowList, array $excludeList ): bool {
		if ( $this->isNullOrigin() ) {
			return false;
		}

		foreach ( $this->getOriginList() as $origin ) {
			if ( !self::matchSingleOrigin( $origin, $allowList, $excludeList ) ) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Checks whether the origin matches at list one of the provided rules in $allowList.
	 *
	 * @param string $origin
	 * @param array $allowList
	 * @param array $excludeList
	 * @return bool
	 */
	private static function matchSingleOrigin( string $origin, array $allowList, array $excludeList ): bool {
		foreach ( $allowList as $rule ) {
			if ( preg_match( self::wildcardToRegex( $rule ), $origin ) ) {
				// Rule matches, check exceptions
				foreach ( $excludeList as $exc ) {
					if ( preg_match( self::wildcardToRegex( $exc ), $origin ) ) {
						return false;
					}
				}

				return true;
			}
		}

		return false;
	}

	/**
	 * Private constructor. Use the public static functions for public access.
	 *
	 * @param string[] $input
	 */
	private function __construct( array $input ) {
		if ( count( $input ) !== 1 ) {
			$this->error( 'Only a single Origin header field allowed in HTTP request' );
		}
		$this->setInput( trim( $input[0] ) );
	}

	private function execute() {
		if ( $this->input === 'null' ) {
			$this->isNullOrigin = true;
		} else {
			$this->isNullOrigin = false;
			$this->origins = preg_split( '/\s+/', $this->input );
			if ( count( $this->origins ) === 0 ) {
				$this->error( 'Origin header must contain at least one origin' );
			}
		}
	}

	/**
	 * Helper function to convert wildcard string into a regex
	 * '*' => '.*?'
	 * '?' => '.'
	 *
	 * @param string $wildcard String with wildcards
	 * @return string Regular expression
	 */
	private static function wildcardToRegex( $wildcard ) {
		$wildcard = preg_quote( $wildcard, '/' );
		$wildcard = str_replace(
			[ '\*', '\?' ],
			[ '.*?', '.' ],
			$wildcard
		);

		return "/^https?:\/\/$wildcard$/";
	}
}