composer: * mediawiki/mediawiki-codesniffer: 36.0.0 → 37.0.0 The following sniffs now pass and were enabled: * Generic.ControlStructures.InlineControlStructure * MediaWiki.PHPUnit.AssertCount.NotUsed npm: * svgo: 2.3.0 → 2.3.1 * https://npmjs.com/advisories/1754 (CVE-2021-33587) Change-Id: I2a9bbee2fecbf7259876d335f565ece4b3622426
157 lines
3.5 KiB
PHP
157 lines
3.5 KiB
PHP
<?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$/";
|
|
}
|
|
}
|