Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.00% covered (success)
95.00%
19 / 20
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
WMFBaseDomainExtractor
95.00% covered (success)
95.00%
19 / 20
66.67% covered (warning)
66.67%
2 / 3
8
0.00% covered (danger)
0.00%
0 / 1
 getCookieDomain
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 matchBaseHostname
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
4.07
 extractSubdomain
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MobileFrontend;
4
5use MediaWiki\MediaWikiServices;
6
7/**
8 * Utility class to find base domain for given host.
9 *
10 * This class contains a hardcoded list of all WMF hosts and WMF specific domain logic. As we never
11 * experienced any bug requests from users and we do not change domains too often there is no need
12 * to put that hosts list into settings.
13 *
14 * @see T148975
15 */
16class WMFBaseDomainExtractor implements BaseDomainExtractorInterface {
17    /**
18     * @var string[]
19     */
20    private $wmfWikiHosts = [
21        'wikipedia.org',
22        'wikibooks.org',
23        'wikiversity.org',
24        'wikinews.org',
25        'wiktionary.org',
26        'wikisource.org',
27        'wikiquote.org',
28        'wikivoyage.org',
29        'wikidata.org',
30        'mediawiki.org',
31        // local vagrant instances
32        'local.wmftest.net'
33    ];
34    /**
35     * @var string[]
36     */
37    private $wmfMultiDomainWikiHosts = [
38        // commons, office, meta, outreach, wikimania, incubator, etc...
39        '.wikimedia.org',
40        // beta cluster
41        '.beta.wmflabs.org',
42        // all other labs
43        '.wmflabs.org'
44    ];
45
46    /**
47     * Although some browsers will accept cookies without the initial . in domain
48     * RFC 2109 requires it to be included.
49     *
50     * $server must be a valid URL (i.e. with the scheme included, http or https
51     * or protocol-relative e.g. //en.m.wikipedia.org'). NULL and empty strings
52     * can also be taken but will return NULL or empty string respectively.
53     *
54     * @inheritDoc
55     */
56    public function getCookieDomain( $server ) {
57        // Per http://php.net/manual/en/function.parse-url.php,
58        // If the requested component doesn't exist within the given
59        // URL, NULL will be returned. So UrlUtils::parse() will return
60        // null as it calls parse_url() if a valid server URL is not
61        // given except it's an empty string.
62        $services = MediaWikiServices::getInstance();
63        $urlUtils = $services->getUrlUtils();
64        $host = $urlUtils->parse( (string)$server )['host'] ?? '';
65
66        $wikiHost = $this->matchBaseHostname( $host, $this->wmfWikiHosts );
67        if ( $wikiHost !== false ) {
68            return '.' . $wikiHost;
69        }
70
71        $multiWikiHost = $this->matchBaseHostname( $host, $this->wmfMultiDomainWikiHosts );
72        if ( $multiWikiHost !== false ) {
73            return '.' . $this->extractSubdomain( $host, $multiWikiHost );
74        }
75        return $host;
76    }
77
78    /**
79     * Find out whether $hostname matches or is subdomain of any host from $hosts array
80     *
81     * @param string|null $hostname Visited host
82     * @param string[] $hosts Array of all wikimedia hosts
83     * @return bool|string Returns wikimedia host base domain, false when not found
84     */
85    private function matchBaseHostname( $hostname, array $hosts ) {
86        if ( $hostname === null ) {
87            return false;
88        }
89        foreach ( $hosts as $wmfHost ) {
90            if ( str_ends_with( $hostname, $wmfHost ) ) {
91                return $wmfHost;
92            }
93        }
94        return false;
95    }
96
97    /**
98     * Parse $host and return $baseDomain with first subdomain
99     * ex: extractSubdomain('en.commons.wikimedia.org', '.wikimedia.org') => 'commons.wikimedia.org'
100     *
101     * This function assumes that $fullHostname is a subdomain of $baseDomain. Please
102     * do the str_ends_with() check first before calling this function
103     *
104     * @param string $fullHostname
105     * @param string $baseDomain
106     * @return string
107     */
108    private function extractSubdomain( $fullHostname, $baseDomain ) {
109        $length = strlen( $baseDomain );
110
111        $subdomains = explode( '.', substr( $fullHostname, 0, -$length ) );
112        $subdomain = array_pop( $subdomains );
113        return $subdomain . $baseDomain;
114    }
115
116}