blob: 72458d116a4a332a7b69f4c4c5cbfead0d9136c4 [file] [log] [blame]
<?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);