proxy-chooser/updates.php (170 lines of code) (raw):

<?php /** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ require("classes/ApacheMirrors.php"); require("classes/IP2Location.php"); require("classes/CountryCodes.php"); require("classes/config.php"); header("X-Attribution: This site or product includes IP2Proxy LITE data available from https://lite.ip2location.com"); const MIRROR_ARCHIVE = "ARCHIVE"; const MIRROR_CI = "CI"; $requestUri = parse_url($_SERVER['REQUEST_URI']); if(! preg_match('!.*updates.xml.gz$!', $requestUri['path'])) { sendError(404, "Only compressed (updates.xml.gz) version supported"); exit(0); } // Initialize mirror list $mirrors = new ApacheMirrors($CONFIG['mirrorListUrl'], $CONFIG['mirrorCacheFile']); $countryCodes = new CountryCodes(); /** * Create a http error response. After the message is created, excecution is * stopped. * * @param type $httpStatus the http status to report * @param type $httpMessage status line message * @param type $userMessage body message, if not provided, $httpMessage will * be reused */ function sendError($httpStatus, $httpMessage, $userMessage = false) { header("HTTP/1.0 $httpStatus $httpMessage"); header("Content-Type: text/plain"); if($userMessage) { echo $userMessage; } else { echo $httpMessage . "\n"; } exit(1); } /** * Generate a http status 400 and return a list of supported mirrors. * * @param string[] $mirrors */ function generateErrorList(ApacheMirrors $mirrors) { $userMessage = "Please choose one of these mirrors:\n\n"; foreach ($mirrors->getProxyList() as $host => $url) { $userMessage .= sprintf("%s - %s\n", $host, $url); } sendError("400", "Invalid mirror", $userMessage); } /** * Stream the raw updates.xml file and stop execution. The contents is send * unmodified. * * @param string $updatesXML */ function streamRawUpdate($updatesXML) { header("Content-Type: application/x-gzip"); $fid = fopen($updatesXML, "r"); $output = fopen("php://output", "r"); stream_copy_to_stream($fid, $output); fclose($fid); fclose($output); exit(0); } /** * Create an absolute url from the supplied url. If the url is already absolute, * it will be returned unmodified, unless it is a reference to the 'closer.lua' * service of the apache foundation. If it is a reference to 'closer.lua' the * referenced file will be resolved using the supplied mirror. * * @param string $url * @param string $baseHref * @param string $mirrorKey * @param string $mirror * @return string */ function createAbsoluteUrl(string $url, string $baseHref, string $mirrorKey, string $mirror) { $urlParts = parse_url($url); if($mirrorKey != MIRROR_CI && strpos($urlParts['path'], "/dyn/closer.lua") === 0) { // This rewrites calls to closer.lua into direct absolute paths with a // fixed name. This makes the assumption, that the update.xml.gz has // the same status as the referenced parts. // // I.e. if we determine, that the distribution was moved to archive // (updates.xml.gz is found on archive.apache.org), it is expected, that // all resources referenced through closer.lua are also to be found // in the archive. The same holds true for the "regular distribution" // case. $queryParts = []; parse_str($urlParts['query'], $queryParts); return $mirror . $queryParts['filename']; } else if(strpos($urlParts['path'], '/') === 0) { return $url; } else { return $baseHref . $url; } } /** * Read the supplied updates.xml and prepend the supplied mirror. * * @param string $updatesXML * @param string $mirror * @param string $nbVersion */ function streamUpdatesWithMirror($updatesXML, $mirrorKey, $mirror, $nbVersion) { global $CONFIG; $bufferFile = sprintf($CONFIG['cachePattern'], $nbVersion, $mirrorKey); if ((!file_exists($bufferFile)) || filemtime($updatesXML) > filemtime($bufferFile)) { mkdir(dirname($bufferFile), 0774, TRUE); if($mirror) { $baseHref = $mirror . "netbeans/netbeans/$nbVersion/nbms/"; if($mirrorKey == 'CI') { $baseHref = $mirror; } $data = file_get_contents($updatesXML); $doc = new DOMDocument(); $doc->loadXML(gzdecode($data)); foreach ($doc->getElementsByTagName("module") as $mod) { $distribution = $mod->getAttribute("distribution"); $mod->setAttribute("distribution", createAbsoluteUrl($distribution, $baseHref, $mirrorKey, $mirror)); } foreach ($doc->getElementsByTagName("license") as $li) { if($li->hasAttribute("url")) { $url = $li->getAttribute("url"); $li->setAttribute("url", createAbsoluteUrl($url, $baseHref, $mirrorKey, $mirror)); } } $xml = gzencode($doc->saveXML()); } else { $data = file_get_contents($updatesXML); $xml = gzencode($data); } file_put_contents($bufferFile, $xml); } $etag = '"' . md5($bufferFile . filemtime($bufferFile)) . '"'; if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag) { header("HTTP/1.0 304 Not modified"); exit(0); } header("Etag: $etag"); streamRawUpdate($bufferFile); } /** * Based on the requsted URI extract the target netbeans version. * * @param string $request_uri * @return string */ function extractNbVersion($request_uri) { $matches = array(); if (preg_match('!.*/uc-proxy-chooser/([^/]+)/updates.xml.gz!', $request_uri, $matches)) { return $matches[1]; } else { sendError("400", "Invalid URL"); } } /** * Check if the requested netbeans version is distributed via the mirror network * * @param type $nbVersion * @return type */ function isOnMirrors($nbVersion) { $primaryServerUrl = "https://downloads.apache.org/netbeans/netbeans/$nbVersion/nbms/updates.xml"; return urlHasContent($primaryServerUrl); } /** * Check if the requested netbeans version is archived * * @param type $nbVersion * @return type */ function isInArchive($nbVersion) { $primaryServerUrl = "https://archive.apache.org/dist/netbeans/netbeans/$nbVersion/nbms/updates.xml.gz"; return urlHasContent($primaryServerUrl); } function ciUrl($version) { $pathVersion = preg_replace("!\D!", "", $version); return "https://ci-builds.apache.org/job/Netbeans/job/netbeans-TLP/job/netbeans/job/release$pathVersion/lastSuccessfulBuild/artifact/dist/netbeans/nbms/"; } /** * Check if the requested netbeans version is distributed via the mirror network * or only accessible as a nightly build. * * @param type $nbVersion * @return type */ function isCIBuild($nbVersion) { $ciUrl = ciUrl($nbVersion) . "updates.xml.gz"; return urlHasContent($ciUrl); } /** * Check if the supplied url return a non 404 response. * * @param string $url * @return boolean */ function urlHasContent($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_NOBODY, true); curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $code != 404; } // Extract the requested netbeans version from the request URI $nbVersion = extractNbVersion($requestUri['path']); // build the path to the updates.xml $updatesXML = sprintf($CONFIG['updatesPattern'], $nbVersion); // Check that the requested netbeans version is available if(!file_exists($updatesXML)) { error_log("Failed to open updates.xml: " . $updatesXML); sendError("500", "Internal server error"); } // Check the mirror parameter and check if a valid mirror was selected $mirror = FALSE; $mirrorKey = null; if(array_key_exists("mirror", $_GET)) { $mirrorKey = $_GET['mirror']; if($mirrorKey == 'BASE') { // Request wants to fetch base file } else if(! $mirrors->proxyExists($mirrorKey)) { generateErrorList($mirrors); } else { $mirror = $mirrors->getProxy($mirrorKey); } } else { $database = new IP2Location\Database(__DIR__ . "/data/IP2LOCATION-LITE-DB1.BIN"); $database2 = new IP2Location\Database(__DIR__ . "/data/IP2LOCATION-LITE-DB1.IPV6.BIN"); $res = $database->lookup($_SERVER['REMOTE_ADDR']); $res2 = $database2->lookup($_SERVER['REMOTE_ADDR']); $proxyKey = null; if($res && (! $mirrorKey)) { $mirrorKey = $mirrors->getProxyCountryKey($countryCodes->resolveApacheCodeFromIP2Country($res['countryCode'])); } if($res2 && (! $mirrorKey)) { $mirrorKey = $mirrors->getProxyCountryKey($countryCodes->resolveApacheCodeFromIP2Country($res2['countryCode'])); } if($mirrorKey == null) { $mirrorKey = $mirrors->getFallbackProxyKey(); } $mirror = $mirrors->getProxy($mirrorKey); } if (!isOnMirrors($nbVersion)) { if (isInArchive($nbVersion)) { // If version of netbeans is already archived, the nbms need to be // fetched from the apache archive $mirror = "https://archive.apache.org/dist/"; $mirrorKey = MIRROR_ARCHIVE; } else if (isCIBuild($nbVersion)) { // If version of netbeans is still in prerelease state, the nbms need // to be fetched from the CI build system $mirror = ciUrl($nbVersion); $updatesXML = $mirror . "updates.xml.gz"; $mirrorKey = MIRROR_CI; } else { sendError(404, "Not found"); } } streamUpdatesWithMirror($updatesXML, $mirrorKey, $mirror, $nbVersion);