1
0
mirror of https://github.com/e107inc/e107.git synced 2025-08-06 14:46:56 +02:00

Downloads: Basic support for NGINX secure_link_md5

- NEW: "Protection" section in Downloads >> Preferences
- NEW: Basic support for NGINX secure_link_md5 in Downloads plugin
- NEW: When configured with a URL protection mode, Downloads will
       modify the direct download URL and/or the mirror download URL
       to be compatible with the implemented URL protection

Fixes: #3075
This commit is contained in:
Nick Liu
2018-04-30 05:50:19 -05:00
parent cce67aecf4
commit fa08c915a3
5 changed files with 223 additions and 11 deletions

View File

@@ -286,7 +286,11 @@ class download_main_admin_ui extends e_admin_ui
//required - default column user prefs
protected $fieldpref = array('checkboxes', 'download_image', 'download_id', 'download_datestamp', 'download_category', 'download_name', 'download_active', 'download_class', 'fb_order', 'options');
//
// Security modes
protected $security_options = array(
'none' => LAN_DL_SECURITY_MODE_NONE,
'nginx-secure_link_md5' => LAN_DL_SECURITY_MODE_NGINX_SECURELINKMD5
);
// optional - required only in case of e.g. tables JOIN. This also could be done with custom model (set it in init())
//protected $editQry = "SELECT * FROM #release WHERE release_id = {ID}";
@@ -1149,6 +1153,19 @@ $columnInfo = array(
if ($_POST['download_subsub']) $temp['download_subsub'] = '1'; else $temp['download_subsub'] = '0';
if ($_POST['download_incinfo']) $temp['download_incinfo'] = '1'; else $temp['download_incinfo'] = '0';
if ($_POST['download_security_mode'] === 'nginx-secure_link_md5')
{
$temp['download_security_mode'] = $_POST['download_security_mode'];
$temp['download_security_expression'] = $_POST['download_security_expression'];
$temp['download_security_link_expiry'] = $_POST['download_security_link_expiry'];
}
else
{
e107::getConfig('core')->removePref('download_security_mode');
e107::getConfig('core')->removePref('download_security_expression');
e107::getConfig('core')->removePref('download_security_link_expiry');
}
e107::getConfig('core')->setPref($temp)->save(false);
@@ -2115,14 +2132,15 @@ $columnInfo = array(
"ASC" => DOWLAN_62,
"DESC" => DOWLAN_63
);
$text = "
<ul class='nav nav-tabs'>
<li class='active'><a data-toggle='tab' href='#core-download-download1'>".LAN_DL_DOWNLOAD_OPT_GENERAL."</a></li>
<li><a data-toggle='tab' href='#core-download-download2'>".LAN_DL_DOWNLOAD_OPT_BROKEN."</a></li>
<li><a data-toggle='tab' href='#core-download-download3'>".LAN_DL_DOWNLOAD_OPT_AGREE."</a></li>
<li><a data-toggle='tab' href='#core-download-download4'>".LAN_DL_UPLOAD."</a></li>
<li><a data-toggle='tab' href='#core-download-download4'>".LAN_DL_DOWNLOAD_OPT_SECURITY."</a></li>
<li><a data-toggle='tab' href='#core-download-download5'>".LAN_DL_UPLOAD."</a></li>
</ul>
<form method='post' action='".e_SELF."?".e_QUERY."'>\n
@@ -2226,6 +2244,39 @@ $columnInfo = array(
</div>
</div>
<div class='tab-pane' id='core-download-download4'>
<div>
<p style='padding: 8px'>
".LAN_DL_SECURITY_DESCRIPTION."
</p>
<table class='table adminform'>
<colgroup>
<col style='width:30%'/>
<col style='width:70%'/>
</colgroup>
<tr>
<td>".LAN_DL_SECURITY_MODE."</td>
<td>".$frm->select('download_security_mode', $this->security_options, $pref['download_security_mode'])."</td>
</tr>
<tbody id='nginx-secure_link_md5' ".($pref['download_security_mode'] === 'nginx-secure_link_md5' ? "" : "style='display:none'").">
<tr>
<td>".LAN_DL_SECURITY_NGINX_SECURELINKMD5_EXPRESSION."</td>
<td>
".$frm->text('download_security_expression', $pref['download_security_expression'], 1024)."
<div class='field-help'>".LAN_DL_SECURITY_NGINX_SECURELINKMD5_EXPRESSION_HELP."</div>
</td>
</tr>
<tr>
<td>".LAN_DL_SECURITY_LINK_EXPIRY."</td>
<td>
".$frm->text('download_security_link_expiry', $pref['download_security_link_expiry'], 16, array('pattern' => '\d+'))."
<div class='field-help'>".LAN_DL_SECURITY_LINK_EXPIRY_HELP."</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class='tab-pane' id='core-download-download5'>
<div>
<table class='table adminform'>
<colgroup>
@@ -2246,7 +2297,20 @@ $columnInfo = array(
</div>
</form>
";
// $ns->tablerender(LAN_DL_OPTIONS, $text);
e107::js('footer-inline', "
$('#download-security-mode').on('change', function() {
var mode = $(this).val();
if (mode == 'nginx-secure_link_md5') {
$('#nginx-secure_link_md5').show('slow');
return;
}
$('#nginx-secure_link_md5').hide('slow');
});
");
echo $text;
}

View File

@@ -0,0 +1,104 @@
<?php
if (!function_exists('http_build_url'))
{
define('HTTP_URL_REPLACE', 1); // Replace every part of the first URL when there's one of the second URL
define('HTTP_URL_JOIN_PATH', 2); // Join relative paths
define('HTTP_URL_JOIN_QUERY', 4); // Join query strings
define('HTTP_URL_STRIP_USER', 8); // Strip any user authentication information
define('HTTP_URL_STRIP_PASS', 16); // Strip any password authentication information
define('HTTP_URL_STRIP_AUTH', 32); // Strip any authentication information
define('HTTP_URL_STRIP_PORT', 64); // Strip explicit port numbers
define('HTTP_URL_STRIP_PATH', 128); // Strip complete path
define('HTTP_URL_STRIP_QUERY', 256); // Strip query string
define('HTTP_URL_STRIP_FRAGMENT', 512); // Strip any fragments (#identifier)
define('HTTP_URL_STRIP_ALL', 1024); // Strip anything but scheme and host
// Build an URL
// The parts of the second URL will be merged into the first according to the flags argument.
//
// @param mixed (Part(s) of) an URL in form of a string or associative array like parse_url() returns
// @param mixed Same as the first argument
// @param int A bitmask of binary or'ed HTTP_URL constants (Optional)HTTP_URL_REPLACE is the default
// @param array If set, it will be filled with the parts of the composed url like parse_url() would return
function http_build_url($url, $parts=array(), $flags=HTTP_URL_REPLACE, &$new_url=false)
{
$keys = array('user','pass','port','path','query','fragment');
// HTTP_URL_STRIP_ALL becomes all the HTTP_URL_STRIP_Xs
if ($flags & HTTP_URL_STRIP_ALL)
{
$flags |= HTTP_URL_STRIP_USER;
$flags |= HTTP_URL_STRIP_PASS;
$flags |= HTTP_URL_STRIP_PORT;
$flags |= HTTP_URL_STRIP_PATH;
$flags |= HTTP_URL_STRIP_QUERY;
$flags |= HTTP_URL_STRIP_FRAGMENT;
}
// HTTP_URL_STRIP_AUTH becomes HTTP_URL_STRIP_USER and HTTP_URL_STRIP_PASS
else if ($flags & HTTP_URL_STRIP_AUTH)
{
$flags |= HTTP_URL_STRIP_USER;
$flags |= HTTP_URL_STRIP_PASS;
}
// Parse the original URL
$parse_url = !is_array($url) ? parse_url($url) : $url;
// Scheme and Host are always replaced
if (isset($parts['scheme']))
$parse_url['scheme'] = $parts['scheme'];
if (isset($parts['host']))
$parse_url['host'] = $parts['host'];
// (If applicable) Replace the original URL with it's new parts
if ($flags & HTTP_URL_REPLACE)
{
foreach ($keys as $key)
{
if (isset($parts[$key]))
$parse_url[$key] = $parts[$key];
}
}
else
{
// Join the original URL path with the new path
if (isset($parts['path']) && ($flags & HTTP_URL_JOIN_PATH))
{
if (isset($parse_url['path']))
$parse_url['path'] = rtrim(str_replace(basename($parse_url['path']), '', $parse_url['path']), '/') . '/' . ltrim($parts['path'], '/');
else
$parse_url['path'] = $parts['path'];
}
// Join the original query string with the new query string
if (isset($parts['query']) && ($flags & HTTP_URL_JOIN_QUERY))
{
if (isset($parse_url['query']))
$parse_url['query'] .= '&' . $parts['query'];
else
$parse_url['query'] = $parts['query'];
}
}
// Strips all the applicable sections of the URL
// Note: Scheme and Host are never stripped
foreach ($keys as $key)
{
if ($flags & (int)constant('HTTP_URL_STRIP_' . strtoupper($key)))
unset($parse_url[$key]);
}
$new_url = $parse_url;
return
((isset($parse_url['scheme'])) ? $parse_url['scheme'] . '://' : '')
.((isset($parse_url['user'])) ? $parse_url['user'] . ((isset($parse_url['pass'])) ? ':' . $parse_url['pass'] : '') .'@' : '')
.((isset($parse_url['host'])) ? $parse_url['host'] : '')
.((isset($parse_url['port'])) ? ':' . $parse_url['port'] : '')
.((isset($parse_url['path'])) ? $parse_url['path'] : '')
.((isset($parse_url['query'])) ? '?' . $parse_url['query'] : '')
.((isset($parse_url['fragment'])) ? '#' . $parse_url['fragment'] : '')
;
}
}

View File

@@ -12,6 +12,7 @@ define("LAN_DL_OPTIONS", "Options"); //FIXME Use Generic
define("LAN_DL_DOWNLOAD_OPT_GENERAL", "General");
define("LAN_DL_DOWNLOAD_OPT_BROKEN", "Reporting");
define("LAN_DL_DOWNLOAD_OPT_AGREE", "Agreements");
define("LAN_DL_DOWNLOAD_OPT_SECURITY", "Protection");
define("LAN_DL_UPLOAD", "Upload"); //FIXME Use Generic
define("LAN_DL_USE_PHP", "Use PHP");
define("LAN_DL_USE_PHP_INFO", "Checking this will send all download requests through PHP");
@@ -228,4 +229,16 @@ define("DOWLAN_HELP_10", "Help for upload options");
// define("DOWLAN_INSTALL_DONE", "Your download plugin is now installed");
// define("DOWLAN_DESCRIPTION", "This plugin is a fully featured Download system");
// define("DOWLAN_CAPTION", "Configure Download");
?>
define("LAN_DL_SECURITY_DESCRIPTION", "Downloads can make use of server-side URL protection features to prevent hotlinking and/or enforce link expiry. " .
"The download server needs to be configured first before setting the options below.");
define("LAN_DL_SECURITY_MODE", "URL protection mode");
define("LAN_DL_SECURITY_MODE_NONE", "None (Default)");
define("LAN_DL_SECURITY_MODE_NGINX_SECURELINKMD5", "NGINX secure_link_md5");
define("LAN_DL_SECURITY_NGINX_SECURELINKMD5_EXPRESSION",
"<a target='_blank' href='https://nginx.org/en/docs/http/ngx_http_secure_link_module.html#secure_link_md5'>NGINX secure_link_md5 expression</a>");
define("LAN_DL_SECURITY_NGINX_SECURELINKMD5_EXPRESSION_HELP", "Same expression as configured on the server");
define("LAN_DL_SECURITY_LINK_EXPIRY", "Duration of validity in seconds");
define("LAN_DL_SECURITY_LINK_EXPIRY_HELP", "Number of seconds the download link should last after being generated. " .
"Only effective if the expression supports expiry time. " .
"Defaults to a very long time if this field is left blank.");

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<e107Plugin name="Downloads" lan="LAN_PLUGIN_DOWNLOAD_NAME" version="1.1" date="2017-04-27" compatibility="2.0" installRequired="true">
<e107Plugin name="Downloads" lan="LAN_PLUGIN_DOWNLOAD_NAME" version="1.2" date="2018-05-01" compatibility="2.0" installRequired="true">
<author name="e107 Inc." url="http://e107.org" />
<description lan="LAN_PLUGIN_DOWNLOAD_DIZ">This plugin is a fully featured File-download system</description>
<description lan="LAN_PLUGIN_DOWNLOAD_DIZ">This plugin is a fully featured file download system</description>
<category>content</category>
<adminLinks>
<link url='admin_download.php' description='LAN_CONFIGURE' icon='images/downloads_32.png' iconSmall='images/downloads_16.png' primary='true' >DOWLAN_CAPTION</link>

View File

@@ -72,7 +72,7 @@ if(strstr(e_QUERY, "mirror"))
}
$sql->update("download", "download_requested = download_requested + 1, download_mirror = '{$mstr}' WHERE download_id = '".intval($download_id)."'");
$sql->update("download_mirror", "mirror_count = mirror_count + 1 WHERE mirror_id = '".intval($mirror_id)."'");
header("Location: {$gaddress}");
header("Location: ".decorate_download_location($gaddress));
exit();
}
@@ -189,7 +189,7 @@ if ($type == "file")
$sql->update("download", "download_requested = download_requested + 1, download_mirror = '{$mstr}' WHERE download_id = '".intval($download_id)."'");
$sql->update("download_mirror", "mirror_count = mirror_count + 1 WHERE mirror_id = '".intval($mirror_id)."'");
header("Location: ".$gaddress);
header("Location: ".decorate_download_location($gaddress));
exit();
}
@@ -217,7 +217,7 @@ if ($type == "file")
if (strstr($download_url, "http://") || strstr($download_url, "ftp://") || strstr($download_url, "https://"))
{
$download_url = e107::getParser()->parseTemplate($download_url,true); // support for shortcode-driven dynamic URLS.
e107::redirect($download_url);
e107::redirect(decorate_download_location($download_url));
// header("Location: {$download_url}");
exit();
}
@@ -435,4 +435,35 @@ function check_download_limits()
}
}
?>
function decorate_download_location($url)
{
$pref = e107::getPref();
if ($pref['download_security_mode'] !== 'nginx-secure_link_md5')
return $url;
$expiry = intval($pref['download_security_link_expiry']);
if ($expiry <= 0)
$expiry = PHP_INT_MAX;
else
$expiry = time() + $expiry;
$url_parts = parse_url($url);
$evaluation = str_replace(
array(
'$secure_link_expires',
'$uri',
'$remote_addr'
),
array(
$expiry,
$url_parts['path'],
$_SERVER['REMOTE_ADDR']
),
$pref['download_security_expression']
);
$query_string = $url_parts['query'];
parse_str($query_string, $query_args);
$query_args['md5'] = md5($evaluation);
if (strpos($pref['download_security_expression'], '$secure_link_expires') !== false)
$query_args['expires'] = $expiry;
require_once(__DIR__.'/includes/shim_http_build_url.php');
return http_build_url($url_parts, array('query' => http_build_query($query_args)));
}