Skip to content

Commit

Permalink
Allow downloadable files to be hosted externally, including AWS
Browse files Browse the repository at this point in the history
Merges zencart#327 and zencart#347

- added support for AWS-hosted downloads (files on AWS S3) ... simply give `aws:bucketname/filename.ext` as the filename in attributes controller, with expiring links to prevent theft
- added support for URL-based downloads, such as Dropbox or any other http/https URL which requires no authentication by the customer (NOTE: offers no theft prevention)
- cleaned up the My Account list of downloads, for simpler styling
- changed both admin and catalog to allow observers to provide details of file availability
- moved download-by-redirect and download-by-streaming to observer class

Notes:
- If using with Dropbox, set the `&dl=0` to `&dl=1` on the "sharing link" that Dropbox gives you, so that the file is immediately downloaded for the customer.
- If sharing from Google Drive, be sure to configure the Sharing Permissions properly, and obtain a shareable link that's got read-only access.

Dev tip:
- For AWS, to keep credentials out of version-control, put them into the `extra-configures` folder, and prefix the filename with `dev-` since that's in the project's `.gitignore` already.
  • Loading branch information
drbyte committed Dec 23, 2017
1 parent a848cdf commit bb471f6
Show file tree
Hide file tree
Showing 13 changed files with 968 additions and 322 deletions.
17 changes: 10 additions & 7 deletions admin/attributes_controller.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<?php
/**
* @package admin
* @copyright Copyright 2003-2016 Zen Cart Development Team
* @copyright Copyright 2003-2017 Zen Cart Development Team
* @copyright Portions Copyright 2003 osCommerce
* @license http://www.zen-cart.com/license/2_0.txt GNU Public License V2.0
* @version $Id: Author: DrByte Thu Mar 3 12:16:32 2016 -0500 Modified in v1.5.5 $
* @version GIT: $Id: Author: DrByte Modified in v1.5.6 $
*/
require('includes/application_top.php');

Expand Down Expand Up @@ -71,10 +71,13 @@
}

if ($action == 'new_cat') {
$sql = "SELECT *
FROM " . TABLE_PRODUCTS_TO_CATEGORIES . "
WHERE categories_id = '" . $current_category_id . "'
ORDER BY products_id";
$sql = "SELECT ptc.*, products_name
FROM " . TABLE_PRODUCTS_TO_CATEGORIES . " ptc
LEFT JOIN " . TABLE_PRODUCTS_DESCRIPTION . " pd
ON ptc.products_id = pd.products_id
AND pd.language_id = '" . (int)$_SESSION['languages_id'] . "'
WHERE ptc.categories_id = '" . $current_category_id . "'
ORDER BY products_name";
$new_product_query = $db->Execute($sql);
$products_filter = (!$new_product_query->EOF) ? $new_product_query->fields['products_id'] : '';
zen_redirect(zen_href_link(FILENAME_ATTRIBUTES_CONTROLLER, 'products_filter=' . $products_filter . '&current_category_id=' . $current_category_id));
Expand Down Expand Up @@ -1644,7 +1647,7 @@ function init() {
$download_display = $db->Execute($download_display_query_raw);
if ($download_display->RecordCount() > 0) {
$filename_is_missing = '';
if (!file_exists(DIR_FS_DOWNLOAD . $download_display->fields['products_attributes_filename'])) {
if ( !zen_orders_products_downloads($download_display->fields['products_attributes_filename']) ) {
$filename_is_missing = zen_image(DIR_WS_IMAGES . 'icon_status_red.gif');
} else {
$filename_is_missing = zen_image(DIR_WS_IMAGES . 'icon_status_green.gif');
Expand Down
19 changes: 8 additions & 11 deletions admin/downloads_manager.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<?php
/**
* @package admin
* @copyright Copyright 2003-2014 Zen Cart Development Team
* @copyright Copyright 2003-2017 Zen Cart Development Team
* @copyright Portions Copyright 2003 osCommerce
* @license http://www.zen-cart.com/license/2_0.txt GNU Public License V2.0
* @version GIT: $Id: Author: DrByte Jun 30 2014 Modified in v1.5.4 $
* @version $Id: Modified in v1.5.6 $
*/

require('includes/application_top.php');
Expand Down Expand Up @@ -147,15 +147,12 @@ function init()
$padInfo = new objectInfo($padInfo_array);
}

// Moved to /admin/includes/configure.php
if (!defined('DIR_FS_DOWNLOAD')) define('DIR_FS_DOWNLOAD', DIR_FS_CATALOG . 'download/');

$filename_is_missing='';
if ( !file_exists(DIR_FS_DOWNLOAD . $products_downloads_query->fields['products_attributes_filename']) ) {
$filename_is_missing = zen_image(DIR_WS_IMAGES . 'icon_status_red.gif');
} else {
$filename_is_missing = zen_image(DIR_WS_IMAGES . 'icon_status_green.gif');
}
$filename_is_missing='';
if ( !zen_orders_products_downloads($products_downloads_query->fields['products_attributes_filename']) ) {
$filename_is_missing = zen_image(DIR_WS_IMAGES . 'icon_status_red.gif');
} else {
$filename_is_missing = zen_image(DIR_WS_IMAGES . 'icon_status_green.gif');
}
?>
<?php
if (isset($padInfo) && is_object($padInfo) && ($products_downloads_query->fields['products_attributes_id'] == $padInfo->products_attributes_id)) {
Expand Down
23 changes: 21 additions & 2 deletions admin/includes/classes/AdminRequestSanitizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ private function filterConvertInt($parameterName)
*/
private function filterFileDirRegex($parameterName)
{
$filedirRegex = '~[^0-9a-z' . preg_quote('.!@#$%^& ()`_+-~/' . '\\', '~') . ']~i';
$filedirRegex = '~[^0-9a-z' . preg_quote('.!@#$%&()_-~/`+^ ' . '\\', '~') . ']~i';
if (isset($_POST[$parameterName])) {
$this->debugMessages[] = 'PROCESSING FILE_DIR_REGEX == ' . $parameterName;
$_POST[$parameterName] = preg_replace($filedirRegex, '', $_POST[$parameterName]);
Expand Down Expand Up @@ -475,7 +475,7 @@ private function filterSanitizeEmailAudience($parameterName)
*/
private function filterProductUrlRegex($parameterName)
{
$urlRegex = '~([^a-z0-9\'!#$&%@();:/=?_\~\[\]-]|[><])~i';
$urlRegex = '~([^0-9a-z' . preg_quote("'.!@#$%&()_-~/;:=?[]", '~') . ']|[><])~i';
if (isset($_POST[$parameterName])) {
$this->debugMessages[] = 'PROCESSING PRODUCT_URL_REGEX == ' . $parameterName;
foreach ($_POST[$parameterName] as $pKey => $pValue) {
Expand All @@ -489,6 +489,25 @@ private function filterProductUrlRegex($parameterName)
}
}

/**
* @param $parameterName
*/
private function filterFilePathOrUrlRegex($parameterName)
{
$regex = '~([^0-9a-z' . preg_quote("'.!@#$%&()_-~/;:=?[]`+^ " . '\\', '~') . ']|[><])~i';
if (isset($_POST[$parameterName])) {
$this->debugMessages[] = 'PROCESSING PRODUCT_URL_REGEX == ' . $parameterName;
foreach ($_POST[$parameterName] as $pKey => $pValue) {
$newValue = filter_var($_POST[$parameterName][$pKey], FILTER_SANITIZE_URL);
if ($newValue === false) {
$newValue = preg_replace($regex, '', $_POST[$parameterName][$pKey]);
}
$_POST[$parameterName][$pKey] = $newValue;
$this->postKeysAlreadySanitized[] = $parameterName;
}
}
}

/**
* @param $parameterName
*/
Expand Down
42 changes: 32 additions & 10 deletions admin/includes/functions/general.php
Original file line number Diff line number Diff line change
Expand Up @@ -2601,7 +2601,7 @@ function zen_has_product_attributes_downloads($products_id, $check_valid=false)
if ($check_valid == true) {
$valid_downloads = '';
while (!$download_display->EOF) {
if (!file_exists(DIR_FS_DOWNLOAD . $download_display->fields['products_attributes_filename'])) {
if (!file_exists(zen_get_download_handler($download_display->fields['products_attributes_filename']))) {
$valid_downloads .= '<br />&nbsp;&nbsp;' . zen_image(DIR_WS_IMAGES . 'icon_status_red.gif') . ' Invalid: ' . $download_display->fields['products_attributes_filename'];
// break;
} else {
Expand Down Expand Up @@ -3361,19 +3361,41 @@ function zen_date_diff($date1, $date2) {
* check that the specified download filename exists on the filesystem
*/
function zen_orders_products_downloads($check_filename) {
global $db;
global $zco_notifier;

$valid_downloads = true;
if (!defined('DIR_FS_DOWNLOAD')) define('DIR_FS_DOWNLOAD', DIR_FS_CATALOG . 'download/');
$handler = zen_get_download_handler($check_filename);

if (!file_exists(DIR_FS_DOWNLOAD . $check_filename)) {
$valid_downloads = false;
// break;
} else {
$valid_downloads = true;
if ($handler == 'local') {
return file_exists(DIR_FS_DOWNLOAD . $check_filename);
}

return $valid_downloads;
/**
* An observer hooking this notifier should set $handler to blank if it tries a validation and fails.
* Or, if validation passes, simply set $handler to the service name (first chars before first colon in filename)
* Or, or there is no way to verify, do nothing to $handler.
*/
$zco_notifier->notify('NOTIFY_TEST_DOWNLOADABLE_FILE_EXISTS', $check_filename, $handler);

// if handler is set but isn't local (internal) then we simply return true since there's no way to "test"
if ($handler != '') return true;

// else if the notifier caused $handler to be empty then that means it failed verification, so we return false
return false;
}

/**
* check if the specified download filename matches a handler for an external download service
* If yes, it will be because the filename contains colons as delimiters ... service:filename:filesize
*/
function zen_get_download_handler($filename) {
$file_parts = explode(':', $filename);

// if the filename doesn't contain any colons, then there's no delimiter to return, so must be using built-in file handling
if (sizeof($file_parts) < 2) {
return 'local';
}

return $file_parts[0];
}

/**
Expand Down
6 changes: 5 additions & 1 deletion admin/includes/init_includes/init_sanitize.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
'SANITIZE_EMAIL_AUDIENCE' => array('type' => 'builtin'),
'PRODUCT_DESC_REGEX' => array('type' => 'builtin'),
'PRODUCT_URL_REGEX' => array('type' => 'builtin'),
'FILE_PATH_OR_URL' => array('type' => 'builtin'),
'CURRENCY_VALUE_REGEX' => array('type' => 'builtin'),
'FLOAT_VALUE_REGEX' => array('type' => 'builtin'),
'PRODUCT_NAME_DEEP_REGEX' => array('type' => 'builtin'),
Expand Down Expand Up @@ -182,7 +183,7 @@
);
$sanitizer->addSimpleSanitization('CONVERT_INT', $group);

$group = array('img_dir', 'products_previous_image', 'products_image_manual', 'products_attributes_filename', 'manufacturers_image_manual');
$group = array('img_dir', 'products_previous_image', 'products_image_manual', 'manufacturers_image_manual');
$sanitizer->addSimpleSanitization('FILE_DIR_REGEX', $group);

$group = array(
Expand Down Expand Up @@ -224,6 +225,9 @@
$group = array('products_url', 'manufacturers_url');
$sanitizer->addSimpleSanitization('PRODUCT_URL_REGEX', $group);

$group = array('products_attributes_filename');
$sanitizer->addSimpleSanitization('FILE_PATH_OR_URL', $group);

$group = array('coupon_min_order');
$sanitizer->addSimpleSanitization('CURRENCY_VALUE_REGEX', $group);

Expand Down

0 comments on commit bb471f6

Please sign in to comment.