* @license GPL-3+ * */ class Downloads { /** */ function __construct() { } /** * @param string $ua * * @return array * * @todo unit tests or use something else * Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_4_11; fr) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/4.1.2 Safari/533.18.5 * Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20110330 Firefox/4.0 * other old ones * * @todo refactor this */ public static function get_platform($ua = null) { if ($ua == '') return array( 'arch' => 'i586', 'system' => 'unknown', 'locale' => 'en', 'browser' => null ); $locale = null; if (preg_match_all('/([.^\(^\)]*) \((.*)\) (.*)/', $ua, $r)) { $r = $r[2][0]; $r = explode(';', $r); if (isset($r[3])) { $r = explode(')', trim($r[3])); if (strlen($r[0]) > 5) $r = substr($r[0], 0, 5); else $r = $r[0]; } else $r = null; $locale = $r; } $arch = 'i586'; if (strpos($ua, 'x86_64') !== false) $arch = 'x86_64'; $sys = null; if (strpos($ua, 'Windows') !== false) $sys = 'win'; elseif (strpos($ua, 'Macintosh') !== false || strpos($ua, 'Mac OS X') !== false) $sys = 'mac'; elseif (strpos($ua, 'Linux') !== false) $sys = 'linux'; $browser = null; if (strpos($ua, 'Firefox') !== false) $browser = 'firefox'; elseif (strpos($ua, 'MSIE') !== false) $browser = 'msie'; elseif (strpos($ua, 'Safari') !== false) $browser = 'safari'; elseif (strpos($ua, 'Opera') !== false) $browser = 'opera'; return array( 'arch' => $arch, 'system' => $sys, 'locale' => $locale, // FIXME (rda) use Accept-Language instead 'browser' => $browser ); } /** * Sort 2D array by multiple associative or numeric keys. * $sorted_array = self::sort_2d_array_by_multiple_keys($unsorted_array, 'first key', 'second', ...); * * Note that keys are preserved! * * @param array $unsorted_array * * @param string first key to order by * * @param string second key to order by * * @param string add as many keys to order by as needed * * @return array $sorted_array */ public static function sort_2d_array_by_multiple_keys() { $arguments = func_get_args(); $unsorted_array = array_shift($arguments); $sort_order = $arguments; uasort($unsorted_array, function($first_value, $second_value) use($sort_order) { $result = 0; for($argument_num = 1, $num_of_arguments = count($sort_order); $argument_num <= $num_of_arguments; $argument_num += 1) { $key = $sort_order[$argument_num - 1]; $first_compare_value = $first_value[$key]; $second_compare_value = $second_value[$key]; $comparison = strcoll($first_compare_value, $second_compare_value); if(0 == $comparison) { $temp_result = 0; } else { $temp_result = (0 > $comparison) ? -1 : 1; } $result += $temp_result; $result = 10 * $result; } return $result; }); return $unsorted_array; } /** * Get mirrors list from mirrors.mageia.org, * store/cache it in a different key/value format * (keys are: "$country" and "_C:$continent"), * and return it. * * Note that the mirrors list doesn't change with versions, for now; * it's a full or nothing list. * * @param boolean $prod * @param boolean $documentation * @param boolean $mirrorlist * * @return array */ public static function get_all_mirrors($prod = true, $documentation = false, $mirrorlist = false) { if ($documentation) { $cache_file = realpath(__DIR__ . '/cached.list_doc.php'); } else if ($mirrorlist) { $cache_file = realpath(__DIR__ . '/cached.list_mirrorlist.php'); } else { $cache_file = realpath(__DIR__ . '/cached.list.php'); } if ($prod) { require $cache_file; } else { $data = file('http://mirrors.mageia.org/api/mageia.9.i586.list'); $mirrors = array(); $num_up = 0; $num_dn = 0; $mirror_cities = array(); $mirror_countries = array(); $refresh_country_and_city_arrays = false; $faults = array(); if ($prod == false && $documentation == false && $mirrorlist == false) { function _r($passthru) {return '_r("' . $passthru . '")';}; include_once(realpath(__DIR__) . '/../en/downloads/get/lib.php'); $refresh_country_and_city_arrays = true; } $num_of_all_mirrs = count($data); $num_of_tested_mirrs = 0; $common_patern = '/distrib/8/i586'; $common_patern = '/distrib/9/i586'; //~ $common_patern = '/distrib/cauldron/i586'; // this huge regex magic achieved with a lot of help from great https://regex101.com/ $single_mirror_parsing_regex = '/\s*continent\s*=\s*(?\w*)\s*,\s*'; $single_mirror_parsing_regex .= '\s*zone\s*=\s*(?\w*)\s*,\s*'; $single_mirror_parsing_regex .= '\s*country\s*=\s*(?\w*)\s*,\s*'; $single_mirror_parsing_regex .= '(?:\s*city\s*=\s*(?[\S ,]*)\s*,\s*)?'; $single_mirror_parsing_regex .= '\s*latitude\s*=\s*[-]?\d*\.?\d*\s*,\s*'; $single_mirror_parsing_regex .= '\s*longitude\s*=\s*[-]?\d*\.?\d*\s*,\s*'; $single_mirror_parsing_regex .= '\s*version\s*=\s*\w*\s*,\s*arch\s*=\s*\w*\s*,\s*type\s*=\s*\w*\s*,\s*'; $single_mirror_parsing_regex .= '\s*url\s*=\s*(?\S*)\s*/m'; foreach ($data as $line) { $num_of_tested_mirrs++; $mirrs_processed = sprintf("%.0f %%", $num_of_tested_mirrs / $num_of_all_mirrs * 100); $regex_error = preg_match_all($single_mirror_parsing_regex, $line, $matches, PREG_SET_ORDER, 0); if (false === $regex_error) { $faults['error: regex parse failed'][] = $line; echo 'Regex parse failed error in line ' . $line; continue; } $m = $matches[0]; if ('' == $m['url']) { $faults['error: parse url'][] = $line; echo 'Url parse error in line ' . $line; continue; } if ('' == $m['continent']) { $faults['error: parse continent'][] = $line; echo 'Continent parse error in line ' . $line; continue; } else { $mirr_continent = $m['continent']; } if (false === strpos($m['url'], $common_patern)) { $faults['error: url distrib missing'][] = $line; echo "Url no $common_patern error in line $line"; continue; } $pu = parse_url($m['url']); if (in_array($pu['scheme'], array('http', 'https', 'ftp'))) { $item = array( 'zone' => ('' != $m['zone']) ? $m['zone'] : '?', 'country' => ('' != $m['country']) ? $m['country'] : '?', 'city' => ('' != $m['city']) ? trim($m['city']) : '-', // BEWARE of the path substitution here. Must match. 'url' => str_replace($common_patern, '', $m['url']) ); if ($refresh_country_and_city_arrays == true) { // prepare details for i18n if ('-' != $item['city']) { if (isset($cities_i18n[$item['city']])) { $mirror_cities[$item['city']] = $cities_i18n[$item['city']]; } else { $mirror_cities[$item['city']] = '_r("' . $item['city'] . '") // new city (not yet in /en/downloads/get/lib.php)'; } } else { $faults['notice: no city given'][] = $line; } if (isset($countries[$item['country']])) { $mirror_countries[$item['country']] = $countries[$item['country']]; } else if (isset($item['country'])) { $mirror_countries[$item['country']] = " // new country (not yet in /en/downloads/get/lib.php)"; } } if ($documentation) { $test_file = $item['url'].'/doc/mga8/date.txt'; } else if ($mirrorlist) { $test_file = $item['url'].'/distrib/9/x86_64/media/core/updates/repodata/repomd.xml'; // when changing, please change $common_patern too $test_file = $item['url'].'/distrib/9/x86_64/media/core/release/meta-task-9-2.mga9.noarch.rpm'; // when changing, please change $common_patern too } else { $test_file = $item['url'].'/iso/9/torrents/Mageia-9-Live-Xfce-i586.torrent'; } if (false === @file_get_contents($test_file)) { $num_dn++; echo "Down $num_dn (up: $num_up, about $mirrs_processed of _all_ mirrors tested) $test_file \n"; } else { $num_up++; echo "Up $num_up (down: $num_dn, about $mirrs_processed of _all_ mirrors tested) $test_file \n"; $mirrors['_C:' . $mirr_continent][] = $item; } } } ksort($mirrors); foreach ($mirrors as &$continent) { $sorted_continent = self::sort_2d_array_by_multiple_keys($continent, 'zone', 'country', 'city', 'url'); $continent = array_values($sorted_continent); } unset($continent); echo "\nThere are $num_up servers with the file and $num_dn with some kind of issue.\n"; file_put_contents($cache_file, sprintf('' . PHP_EOL, var_export($mirrors, true))); if ($refresh_country_and_city_arrays == true) { foreach ($countries as $ISO_3166_country_code => $lib_country) { if (!isset($mirror_countries[$ISO_3166_country_code])) { $mirror_countries[$ISO_3166_country_code] = $lib_country; } } foreach ($cities_i18n as $english_name => $translated_name) { if (!isset($mirror_cities[$english_name])) { $mirror_cities[$english_name] = '_r("' . $english_name . '")'; } } ksort($mirror_cities); $countries_u = array_unique($mirror_countries); ksort($countries_u); echo "\nArray of cities for i18n: "; var_export($mirror_cities); echo ";\nArray of countries for i18n: "; var_export($countries_u); echo ";\nArray of faults and warnings: "; var_export($faults); echo ";\n"; } echo "\nRemove char ' at each value on both arrays for cities and countries above : replace '_r(\" with just _r( and also behind \")', with \"),\n"; echo "and update arrays in file /en/downloads/get/lib.php with that.\n"; } return $mirrors; } /** * Get mirrors from stored dictionary and find best matching mirror: * - if it exists in the country otherwise * - on continent if it exists otherwise * - random mirror * It can also prepare a list of mirrors for mirrorlist page * * @param string $country * @param string $continent * @param boolean $prod * @param boolean $documentation * @param boolean $mirrorlist * * @return array */ function get_mirror($country, $continent = null, $prod = true, $documentation = false, $mirrorlist = false, $https_only = false) { $mirs = self::get_all_mirrors($prod, $documentation, $mirrorlist); $continent = '_C:' . $continent; $mirrors = array(); $fr_mirr_asist = array(); $list_of_mirrs = array( 'country' => array(), 'continent_minus_country' => array(), 'other_continents' => array() ); $hostnames = array(); $list_of_mirrs['country_alternate_protocol'] = array(); $continent_https_only = array(); $other_continents_https_only = array(); foreach ($mirs as $curr_continent => $continent_mirrors) { foreach ($continent_mirrors as $mirror) { if ($continent != $curr_continent) { if ($mirrorlist) { $list_of_mirrs['other_continents'][] = $mirror['url']; } elseif (true === $https_only) { $pu = parse_url($mirror['url']); if ('https' == $pu['scheme']) { $other_continents_https_only[$curr_continent][] = $mirror; } } } else { if (strpos($mirror['url'], 'distrib-coffee.ipsl.jussieu.fr') !== false) { // exclude source server to drop it's DL load continue; } // keep assisting the french mirrors with german ones if ($country == 'FR' && $mirror['country'] == 'DE') { $pu = parse_url($mirror['url']); if ('https' == $pu['scheme']) { $fr_mirr_asist[] = $mirror; } } // only add german mirrors when french are on turn // sorting of mirror db cache must be kept to work properly (DE before FR) if ($country == 'FR' && $mirror['country'] == 'FR' && count($fr_mirr_asist) > 0) { $mirrors[$continent] = $fr_mirr_asist; $fr_mirr_asist = array(); } if ($mirrorlist) { $pu = parse_url($mirror['url']); if (in_array($pu['host'], $hostnames)) { $list_of_mirrs['country_alternate_protocol'][] = $mirror['url']; } else { $hostnames[] = $pu['host']; if ($mirror['country'] == $country) { $list_of_mirrs['country'][] = $mirror['url']; } else { $list_of_mirrs['continent_minus_country'][] = $mirror['url']; } } } else { if ($mirror['country'] == $country) { if (true === $https_only) { $pu = parse_url($mirror['url']); if ('https' == $pu['scheme']) { $mirrors[$continent][] = $mirror; } } else { $mirrors[$continent][] = $mirror; } } else { if (true === $https_only) { $pu = parse_url($mirror['url']); if ('https' == $pu['scheme']) { $continent_https_only[$curr_continent][] = $mirror; } } } } } } } if (count($mirrors) == 0) { if (true === $https_only) { if (count($continent_https_only) > 0) { $mirrors = $continent_https_only; } if (count($mirrors) == 0 && count($other_continents_https_only) > 0) { $mirrors = $other_continents_https_only; } } else { // add all continent mirrors if country doesn't have any $mirrors[$continent] = $continent_mirrors; } } // falback if selection fails if (count($mirrors) > 0) { $mirs = $mirrors; } // a workaround as shuffle() doesn't preserve assoc. keys if (count($mirs) > 1) { $mirs_keys = array_keys($mirs); shuffle($mirs_keys); foreach($mirs_keys as $key) { $shuffled[$key] = $mirs[$key]; } $mirs = $shuffled; } $mirr_continent = array_keys($mirs)[0]; $mirs = array_shift($mirs); shuffle($mirs); $one_mirror = array_shift($mirs); $one_mirror['continent'] = $mirr_continent; if ($mirrorlist) { shuffle($list_of_mirrs['country']); shuffle($list_of_mirrs['continent_minus_country']); shuffle($list_of_mirrs['other_continents']); $resulting_mirrs = array_merge( $list_of_mirrs['country'], $list_of_mirrs['continent_minus_country'], $list_of_mirrs['country_alternate_protocol'], $list_of_mirrs['other_continents'] ); $one_mirror['mirrors_list'] = array_slice($resulting_mirrs, 0, 10); } return $one_mirror; } function prepare_download($force = false, $country = null, $prod = true, $documentation = false, $mirrorlist = false, $https_only = false) { return $this->get_one_mirror($force, $country, $prod, $documentation, $mirrorlist, $https_only); } /** * Setup session data about current visitor for downloads. * * @param boolean $force * @param string $country * @param boolean $prod * @param boolean $documentation * @param boolean $mirrorlist * * @return array * * TODO extract as much as possible $_SESSION(read) and $_SERVER and $_GET */ function get_one_mirror($force = false, $country = null, $prod = true, $documentation = false, $mirrorlist = false, $https_only = false) { $fuzzy_mirror = false; if (!is_null($country)) $force = true; // FIXME break this into smaller parts and extract globals so we can test st if (!$force && isset($_SESSION['dl-data'])) { //error_log(sprintf('Got session data: %s', print_r($_SESSION['dl-data'], true))); $system = $_SESSION['dl-data']['system']; if (isset($_GET['mirror'])) { $mirror = array('url' => $_GET['mirror']); $mirror['purl'] = parse_url($mirror['url']); $_SESSION['dl-data']['mirror'] = $mirror; $country = ''; } else { $country = $_SESSION['dl-data']['country']; $mirror = $_SESSION['dl-data']['mirror']; } } else { //error_log('getting platform'); $system = self::get_platform($_SERVER['HTTP_USER_AGENT']); if (isset($_GET['mirror'])) { $mirror = array('url' => $_GET['mirror']); $mirror['purl'] = parse_url($mirror['url']); $country = null; } else { //error_log('no mirror set yet'); if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $str = $_SERVER['HTTP_X_FORWARDED_FOR']) { $arr = explode(', ', $str); $ip = $arr[0]; } else $ip = $_SERVER['REMOTE_ADDR']; $_SESSION['ip'] = $ip; if (is_null($country)) { require_once realpath(__DIR__ . '/mga_geoip.php'); $country = MGA_Geoip::mga_geoip_country_by_ip($ip, false); $continent = MGA_Geoip::mga_geoip_continent_by_country($country); $fuzzy_mirror = true; $_SESSION['country'] = $country; $_SESSION['continent'] = $continent; } if (!isset($continent)) { $continent = null; } $mirror = $this->get_mirror($country, $continent, $prod, $documentation, $mirrorlist, $https_only); $mirror['purl'] = parse_url($mirror['url']); // reassign country, as get_one_mirror() may have decided // to return a mirror from another country than the one // requested initially - @see get_one_mirror() $country = $mirror['zone']; $continent = $mirror['continent']; if (is_null($mirror)) { // @todo? } } // write to session $_SESSION['dl-data'] = array( 'system' => $system, 'country' => $country, 'continent' => $continent, 'mirror' => $mirror ); } if (!isset($mirror['mirrors_list'])) { $mirror['mirrors_list'] = array(); } return array( 'arch' => $system['arch'], 'mirror_host' => $mirror['purl']['host'], 'mirror_scheme' => $mirror['purl']['scheme'], 'mirror_url' => $mirror['url'], 'country' => $country, 'continent' => $continent, 'city' => $mirror['city'], 'fuzzy_mirror' => $fuzzy_mirror, 'mirrors_list' => $mirror['mirrors_list'], ); } }