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